6.6.2 - Alpha2 - project.json 支持选项名称宽松匹配及别名兼容

This commit is contained in:
SuperMonster003
2025-01-20 20:21:45 +08:00
parent e8cc12dbb8
commit 08c64bf248
12 changed files with 230 additions and 75 deletions

View File

@@ -17,6 +17,7 @@ import org.autojs.autojs.engine.encryption.AdvancedEncryptionStandard
import org.autojs.autojs.pio.PFiles
import org.autojs.autojs.project.BuildInfo
import org.autojs.autojs.project.ProjectConfig
import org.autojs.autojs.project.ProjectConfig.DEFAULT_MAIN_SCRIPT_FILE_NAME
import org.autojs.autojs.script.EncryptedScriptFileHeader.writeHeader
import org.autojs.autojs.script.JavaScriptFileSource
import org.autojs.autojs.util.FileUtils.TYPE.JAVASCRIPT
@@ -105,7 +106,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
srcChildFile.copyTo(File(destDirFile, srcChildFile.name), true)
}
} else {
if (!mProjectConfig.ignoredDirs.contains(srcChildFile)) {
if (!mProjectConfig.excludedDirs.contains(srcChildFile)) {
copyDir(srcChildFile, PFiles.join(relativeDestPath, srcChildFile.name + File.separator))
}
}
@@ -186,7 +187,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
PFiles.write(ProjectConfig.configFileOfDir(config.sourcePath), it.toJson())
}
} ?: ProjectConfig()
.setMainScriptFile("main.js")
.setMainScriptFileName(DEFAULT_MAIN_SCRIPT_FILE_NAME)
.setName(config.name)
.setPackageName(config.packageName)
.setVersionName(config.versionName)
@@ -199,7 +200,7 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
}
projectConfig.run {
mKey = MD5Utils.md5(packageName + versionName + mainScriptFile)
mKey = MD5Utils.md5(packageName + versionName + mainScriptFileName)
mInitVector = MD5Utils.md5(buildInfo.buildId + name).substring(0, 16)
Libs.entries.forEach { entry ->
if (config.libs.contains(entry.label)) {
@@ -281,10 +282,10 @@ open class ApkBuilder(apkInputStream: InputStream?, private val outApkFile: File
val signer = ApkSigner(outApkFile, tmpOutputApk)
signer.useDefaultSignatureVersion = false
signer.v1SigningEnabled = mProjectConfig.signatureSchemes.contains("V1")
signer.v2SigningEnabled = mProjectConfig.signatureSchemes.contains("V2")
signer.v3SigningEnabled = mProjectConfig.signatureSchemes.contains("V3")
signer.v4SigningEnabled = mProjectConfig.signatureSchemes.contains("V4")
signer.v1SigningEnabled = mProjectConfig.signatureScheme.contains("V1")
signer.v2SigningEnabled = mProjectConfig.signatureScheme.contains("V2")
signer.v3SigningEnabled = mProjectConfig.signatureScheme.contains("V3")
signer.v4SigningEnabled = mProjectConfig.signatureScheme.contains("V4")
var keyStoreFile = defaultKeyStoreFile
var password = "AutoJs6"

View File

@@ -26,7 +26,7 @@ import java.io.IOException
open class AssetsProjectLauncher(private val mAssetsProjectDir: String, private val mActivity: Context) {
private val mProjectDir: String = File(mActivity.filesDir, "project/").path
private val mProjectConfig: ProjectConfig = ProjectConfig.fromAssets(mActivity, ProjectConfig.configFileOfDir(mAssetsProjectDir))
private val mMainScriptFile: File = File(mProjectDir, mProjectConfig.mainScriptFile)
private val mMainScriptFile: File = File(mProjectDir, mProjectConfig.mainScriptFileName)
private val mHandler: Handler = Handler(Looper.getMainLooper())
private var mScriptExecution: ScriptExecution? = null
@@ -95,7 +95,7 @@ open class AssetsProjectLauncher(private val mAssetsProjectDir: String, private
}
private fun initKey(projectConfig: ProjectConfig) {
val key = MD5.md5(projectConfig.packageName + projectConfig.versionName + projectConfig.mainScriptFile)
val key = MD5.md5(projectConfig.packageName + projectConfig.versionName + projectConfig.mainScriptFileName)
val vec = MD5.md5(projectConfig.buildInfo.buildId + projectConfig.name).substring(0, 16)
try {
val fieldKey = ScriptEncryption::class.java.getDeclaredField("mKey")

View File

@@ -26,7 +26,7 @@ public class ProjectTemplate {
return Observable.fromCallable(() -> {
mProjectDir.mkdirs();
PFiles.write(ProjectConfig.configFileOfDir(mProjectDir.getPath()), mProjectConfig.toJson());
new File(mProjectDir, mProjectConfig.getMainScriptFile()).createNewFile();
new File(mProjectDir, mProjectConfig.getMainScriptFileName()).createNewFile();
return mProjectDir;
})
.subscribeOn(Schedulers.io())

View File

@@ -1,20 +1,33 @@
package org.autojs.autojs.project;
import com.google.gson.annotations.SerializedName;
import org.autojs.autojs.annotation.SerializedNameCompatible;
import org.autojs.autojs.annotation.SerializedNameCompatible.With;
import java.util.zip.CRC32;
public class BuildInfo {
@SerializedName(value = "buildTime", alternate = {"build_time"})
private long mBuildTime;
@SerializedName(value = "buildId", alternate = {"build_id"})
@SerializedName(value = "id")
@SerializedNameCompatible(with = {@With(value = "build_id", target = {"AutoJs4", "AutoX"})})
private String mBuildId;
@SerializedName(value = "buildNumber", alternate = {"build_number"})
@SerializedName(value = "number")
@SerializedNameCompatible(with = {@With(value = "build_number", target = {"AutoJs4", "AutoX"})})
private long mBuildNumber;
@SerializedName(value = "time")
@SerializedNameCompatible(with = {@With(value = "build_time", target = {"AutoJs4", "AutoX"})})
private long mBuildTime;
public String getBuildId() {
return mBuildId;
}
public void setBuildId(String buildId) {
mBuildId = buildId;
}
public long getBuildNumber() {
return mBuildNumber;
}
@@ -31,14 +44,6 @@ public class BuildInfo {
mBuildTime = buildTime;
}
public String getBuildId() {
return mBuildId;
}
public void setBuildId(String buildId) {
mBuildId = buildId;
}
public static BuildInfo generate(long buildNumber) {
BuildInfo info = new BuildInfo();
info.setBuildNumber(buildNumber);

View File

@@ -9,6 +9,8 @@ import androidx.annotation.Nullable;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import org.autojs.autojs.annotation.SerializedNameCompatible;
import org.autojs.autojs.annotation.SerializedNameCompatible.With;
import org.autojs.autojs.apkbuilder.keystore.KeyStore;
import org.autojs.autojs.model.explorer.ExplorerPage;
import org.autojs.autojs.pio.PFiles;
@@ -21,8 +23,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -34,65 +38,123 @@ public class ProjectConfig {
public static final String CONFIG_FILE_NAME = "project.json";
public static final String DEFAULT_MAIN_SCRIPT_FILE_NAME = "main.js";
private static final Gson sGson = new GsonBuilder()
.registerTypeAdapter(ProjectConfig.class, new JsonUtils.FuzzyDeserializer<ProjectConfig>())
.registerTypeAdapter(LaunchConfig.class, new JsonUtils.FuzzyDeserializer<LaunchConfig>())
.registerTypeAdapter(ScriptConfig.class, new JsonUtils.FuzzyDeserializer<ScriptConfig>())
.registerTypeAdapter(BuildInfo.class, new JsonUtils.FuzzyDeserializer<BuildInfo>())
.setPrettyPrinting()
.create();
@SerializedName("name")
@SerializedNameCompatible(with = {@With(value = "projectName")})
private String mName;
@SerializedName("versionName")
@SerializedNameCompatible(with = {@With(value = "version")})
private String mVersionName;
@SerializedName("versionCode")
private int mVersionCode = -1;
@SerializedName("packageName")
@SerializedNameCompatible(with = {@With(value = "package")})
private String mPackageName;
@SerializedName("main")
private String mMainScriptFile;
@SerializedNameCompatible(with = {
@With(value = "mainName"),
@With(value = "mainScript"),
@With(value = "mainScriptName"),
@With(value = "mainScriptFile"),
@With(value = "mainScriptFileName"),
@With(value = "mainFile"),
@With(value = "mainFileName"),
})
private String mMainScriptFileName;
@Nullable
@SerializedName(value = "assets", alternate = {"asset", "assetList"})
@SerializedName(value = "assets")
@SerializedNameCompatible(with = {
@With(value = "asset"),
@With(value = "assetList"),
})
private List<String> mAssets = new ArrayList<>();
@SerializedName("launchConfig")
@SerializedNameCompatible(with = {@With(value = "launch")})
private LaunchConfig mLaunchConfig = new LaunchConfig();
@SerializedName("build")
@SerializedNameCompatible(with = {@With(value = "buildInfo")})
private BuildInfo mBuildInfo = new BuildInfo();
@SerializedName("icon")
@SerializedNameCompatible(with = {@With(value = "iconPath")})
private String mIconPath;
private transient Callable<Bitmap> mIconBitmapGetter;
@Nullable
@SerializedName(value = "abis", alternate = {"abi", "abiList"})
@SerializedName(value = "abis")
@SerializedNameCompatible(with = {
@With(value = "abi"),
@With(value = "abiList"),
})
private List<String> mAbis = new ArrayList<>();
@Nullable
@SerializedName(value = "libs", alternate = {"lib", "libList"})
@SerializedName(value = "libs")
@SerializedNameCompatible(with = {
@With(value = "lib"),
@With(value = "libList"),
})
private List<String> mLibs = new ArrayList<>();
@SerializedName("permissions")
@SerializedNameCompatible(with = {
@With(value = "permission"),
@With(value = "permissionList"),
})
private List<String> mPermissions = new ArrayList<>();
@SerializedName("signatureSchemes")
private String mSignatureSchemes = "V1 + V2";
@SerializedName("signatureScheme")
@SerializedNameCompatible(with = {
@With(value = "signatureSchemes"),
@With(value = "signature"),
})
private String mSignatureScheme = "V1 + V2";
@Nullable
private transient KeyStore mKeyStore = null;
@SerializedName("scripts")
@SerializedName("scriptConfigs")
@SerializedNameCompatible(with = {
@With(value = "scriptsConfigs"),
@With(value = "scriptsConfig"),
@With(value = "scriptConfig"),
@With(value = "scripts", target = {"AutoJs4", "AutoX"}),
})
private final Map<String, ScriptConfig> mScriptConfigs = new HashMap<>();
@SerializedName(value = "useFeatures", alternate = {"useFeature", "useFeatureList"})
@SerializedName(value = "useFeatures")
@SerializedNameCompatible(with = {
@With(value = "useFeature"),
@With(value = "useFeatureList"),
@With(value = "features"),
@With(value = "feature"),
@With(value = "featureList"),
})
private List<String> mFeatures = new ArrayList<>();
@SerializedName("ignoredDirs")
private final List<File> mIgnoredDirs = new ArrayList<>();
@SerializedName("excludedDirs")
@SerializedNameCompatible(with = {
@With(value = "ignoredDirs", target = {"AutoJs4", "AutoX"}),
@With(value = "ignore", target = {"Unknown"}),
})
private final List<File> mExcludedDirs = new ArrayList<>();
@Nullable
private transient String mSourcePath = null;
@@ -118,7 +180,7 @@ public class ProjectConfig {
if (TextUtils.isEmpty(config.getVersionName())) {
return false;
}
if (TextUtils.isEmpty(config.getMainScriptFile())) {
if (TextUtils.isEmpty(config.getMainScriptFileName())) {
return false;
}
return config.getVersionCode() != -1;
@@ -179,7 +241,7 @@ public class ProjectConfig {
setFieldIfMatches(versionNamePattern, s, projectConfig::setVersionName);
setFieldForIntIfMatches(versionCodePattern, s, projectConfig::setVersionCode);
setFieldIfMatches(packageNamePattern, s, projectConfig::setPackageName);
setFieldIfMatches(mainPattern, s, projectConfig::setMainScriptFile);
setFieldIfMatches(mainPattern, s, projectConfig::setMainScriptFileName);
setFieldIfMatches(iconPattern, s, projectConfig::setIconPath);
setListIfMatches(assetsPattern, s, projectConfig::setAssets);
@@ -340,13 +402,21 @@ public class ProjectConfig {
return this;
}
@NonNull
/**
* @deprecated This method is deprecated. Use {@link #getMainScriptFileName()} instead.
*/
@Deprecated
public String getMainScriptFile() {
return mMainScriptFile != null ? mMainScriptFile : "main.js";
return getMainScriptFileName();
}
public ProjectConfig setMainScriptFile(String mainScriptFile) {
mMainScriptFile = mainScriptFile;
@NonNull
public String getMainScriptFileName() {
return mMainScriptFileName != null ? mMainScriptFileName : DEFAULT_MAIN_SCRIPT_FILE_NAME;
}
public ProjectConfig setMainScriptFileName(String mainScriptFileName) {
mMainScriptFileName = mainScriptFileName;
return this;
}
@@ -439,40 +509,36 @@ public class ProjectConfig {
@NonNull
public List<String> getFeatures() {
if (mFeatures == null) {
mFeatures = Collections.emptyList();
}
return mFeatures;
}
public void setFeatures(List<String> features) {
public void setFeatures(@NonNull List<String> features) {
mFeatures = features;
}
public ScriptConfig getScriptConfig(String path) {
ScriptConfig config = mScriptConfigs.get(path);
if (config == null) {
config = new ScriptConfig();
}
if (mFeatures.isEmpty()) {
return config;
}
ArrayList<String> features = new ArrayList<>(config.getFeatures());
for (String feature : mFeatures) {
if (!features.contains(feature)) {
features.add(feature);
}
}
config.setFeatures(features);
return config;
ScriptConfig scriptConfig = Objects.requireNonNull(mScriptConfigs.getOrDefault(path, new ScriptConfig()));
List<String> combinedFeatures = getCombinedFeatures(scriptConfig);
scriptConfig.setFeatures(combinedFeatures);
return scriptConfig;
}
public List<File> getIgnoredDirs() {
return mIgnoredDirs;
@NotNull
public ArrayList<String> getCombinedFeatures(ScriptConfig scriptConfig) {
return new ArrayList<>(
new HashSet<>() {{
addAll(scriptConfig.getFeatures());
addAll(mFeatures);
}}
);
}
public ProjectConfig ignoredDir(File ignoredDir) {
mIgnoredDirs.add(ignoredDir);
public List<File> getExcludedDirs() {
return mExcludedDirs;
}
public ProjectConfig excludeDir(File dirToExclude) {
mExcludedDirs.add(dirToExclude);
return this;
}
@@ -495,12 +561,12 @@ public class ProjectConfig {
return this;
}
public String getSignatureSchemes() {
return mSignatureSchemes;
public String getSignatureScheme() {
return mSignatureScheme;
}
public ProjectConfig setSignatureSchemes(String signatureSchemes) {
mSignatureSchemes = signatureSchemes;
public ProjectConfig setSignatureScheme(String signatureScheme) {
mSignatureScheme = signatureScheme;
return this;
}
@@ -513,4 +579,5 @@ public class ProjectConfig {
mKeyStore = keyStore;
return this;
}
}

View File

@@ -23,7 +23,7 @@ public class ProjectLauncher {
public ProjectLauncher(@NonNull String projectDir) {
mProjectDir = projectDir;
mProjectConfig = ProjectConfig.fromProjectDir(projectDir);
mMainScriptFile = new File(mProjectDir, mProjectConfig == null ? "main.js" : mProjectConfig.getMainScriptFile());
mMainScriptFile = new File(mProjectDir, mProjectConfig == null ? ProjectConfig.DEFAULT_MAIN_SCRIPT_FILE_NAME : mProjectConfig.getMainScriptFileName());
}
public void launch(ScriptEngineService service) {

View File

@@ -1,9 +1,19 @@
package org.autojs.autojs.project
import com.google.gson.annotations.SerializedName
import org.autojs.autojs.annotation.SerializedNameCompatible
import org.autojs.autojs.annotation.SerializedNameCompatible.With
data class ScriptConfig @JvmOverloads constructor(
@SerializedName("useFeatures") var features: List<String> = emptyList(),
@SerializedName("useFeatures")
@field:SerializedNameCompatible(
With(value = "useFeature"),
With(value = "useFeatureList"),
With(value = "features"),
With(value = "feature"),
With(value = "featureList"),
)
var features: List<String> = emptyList(),
) {
fun hasFeature(feature: String) = features.contains(feature)

View File

@@ -798,7 +798,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
ProjectConfig projectConfig;
if (mProjectConfig != null) {
projectConfig = mProjectConfig
.ignoredDir(new File(mSource, mProjectConfig.getBuildDir()))
.excludeDir(new File(mSource, mProjectConfig.getBuildDir()))
.setSourcePath(mSource)
.setIconPath(mProjectConfig.getIconPath() == null ? null : new File(mSource, mProjectConfig.getIconPath()).getPath());
} else {
@@ -815,7 +815,7 @@ public class BuildActivity extends BaseActivity implements ApkBuilder.ProgressCa
.setAbis(abis)
.setLibs(libs)
.setKeyStore(mVerifiedKeyStores.getSelectedItemPosition() > 0 ? (KeyStore) mVerifiedKeyStores.getSelectedItem() : null)
.setSignatureSchemes(mSignatureSchemes.getSelectedItem().toString())
.setSignatureScheme(mSignatureSchemes.getSelectedItem().toString())
.setPermissions(permissions);
}

View File

@@ -125,7 +125,7 @@ class ProjectConfigActivity : BaseActivity() {
mVersionCode.setText(config.versionCode.coerceAtLeast(0).toString())
mPackageName.setText(config.packageName)
mVersionName.setText(config.versionName)
mMainFileName.setText(config.mainScriptFile)
mMainFileName.setText(config.mainScriptFileName)
mProjectLocationWrapper.visibility = View.GONE
config.iconPath?.let { icon ->
File(mDirectory, icon).takeIf { it.exists() }?.let { iconFile ->
@@ -197,7 +197,7 @@ class ProjectConfigActivity : BaseActivity() {
it.name = mAppName.text.toString()
it.versionCode = mVersionCode.text.toString().toInt()
it.versionName = mVersionName.text.toString()
it.mainScriptFile = mMainFileName.text.toString()
it.mainScriptFileName = mMainFileName.text.toString()
it.packageName = mPackageName.text.toString()
}
if (mNewProject) {

View File

@@ -1,7 +1,15 @@
package org.autojs.autojs.util
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
import org.autojs.autojs.annotation.SerializedNameCompatible
import org.json.JSONArray
import org.json.JSONObject
import java.lang.reflect.Field
import java.lang.reflect.Type
/**
* Created by SuperMonster003 on Nov 20, 2024.
@@ -70,7 +78,7 @@ object JsonUtils {
closeBraces++
}
while (closeBraces > openBraces) {
json = '{' + json
json = "{$json"
openBraces++
}
return json
@@ -78,4 +86,67 @@ object JsonUtils {
fun isValidJson(json: String): Boolean = runCatching { JSONObject(json) }.isSuccess || runCatching { JSONArray(json) }.isSuccess
class FuzzyDeserializer<T> : JsonDeserializer<T> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): T {
require(typeOfT is Class<*>) {
"Expected parameter typeOfT to be of type Class, but got: ${typeOfT?.javaClass?.name}"
}
@Suppress("UNCHECKED_CAST")
val clazz = typeOfT as Class<T>
val instance = try {
clazz.getDeclaredConstructor().newInstance()
} catch (e: Exception) {
throw RuntimeException("Failed to create an instance of ${clazz.name}", e)
}
if (json is JsonObject) {
clazz.declaredFields.forEach { processField(it, json, instance, context) }
}
return instance
}
private fun processField(field: Field, json: JsonObject, instance: T, context: JsonDeserializationContext?) {
field.isAccessible = true
val serializedNameAnnotation = field.getAnnotation(SerializedName::class.java)
val compatibleAnnotation = field.getAnnotation(SerializedNameCompatible::class.java)
val sanitizedPrimaryName = sanitizeKey(serializedNameAnnotation?.value ?: field.name)
val sanitizedAlternateNames = serializedNameAnnotation?.alternate?.map { sanitizeKey(it) } ?: emptyList()
val sanitizedCompatibleNames = compatibleAnnotation?.with?.map { sanitizeKey(it.value) to it.isReversed } ?: emptyList()
val serializedNames: List<Pair<String, Boolean>> = (sanitizedAlternateNames + sanitizedPrimaryName).map { it to false } + sanitizedCompatibleNames
for ((jsonKey, jsonValue) in json.entrySet()) {
val sanitizedJsonKey = sanitizeKey(jsonKey)
for (pair in serializedNames) {
val (serializedKey, isReversed) = pair
if (sanitizedJsonKey == serializedKey) {
when (field.type) {
Boolean::class.javaPrimitiveType, Boolean::class.java -> {
if (jsonValue.isJsonPrimitive && jsonValue.asJsonPrimitive.isBoolean) {
val value = jsonValue.asBoolean
field.set(instance, if (isReversed) !value else value)
}
}
else -> {
if (!jsonValue.isJsonNull) {
val value = context?.deserialize<Any>(jsonValue, field.genericType)
field.set(instance, value)
}
}
}
return
}
}
}
}
private fun sanitizeKey(key: String): String {
return key.replace(Regex("[^a-zA-Z0-9]"), "").lowercase()
}
}
}