Merge "Add a simple metadata class option for system feature codegen" into main
diff --git a/aconfig/exported_java_aconfig_library.go b/aconfig/exported_java_aconfig_library.go
index a64cac8..f7e6dcf 100644
--- a/aconfig/exported_java_aconfig_library.go
+++ b/aconfig/exported_java_aconfig_library.go
@@ -16,6 +16,7 @@
import (
"android/soong/android"
+ "strconv"
)
func ExportedJavaDeclarationsLibraryFactory() android.Singleton {
@@ -37,6 +38,16 @@
cacheFiles = append(cacheFiles, decl.IntermediateCacheOutputPath)
})
+ var newExported bool
+ if useNewExported, ok := ctx.Config().GetBuildFlag("RELEASE_ACONFIG_NEW_EXPORTED"); ok {
+ newExported = useNewExported == "true"
+ }
+
+ var newStorage bool
+ if useNewStorage, ok := ctx.Config().GetBuildFlag("RELEASE_READ_FROM_NEW_STORAGE"); ok {
+ newStorage = useNewStorage == "true"
+ }
+
// Generate build action for aconfig
this.intermediatePath = android.PathForIntermediates(ctx, "exported_java_aconfig_library.jar")
ctx.Build(pctx, android.BuildParams{
@@ -45,7 +56,9 @@
Output: this.intermediatePath,
Description: "exported_java_aconfig_library",
Args: map[string]string{
- "cache_files": android.JoinPathsWithPrefix(cacheFiles, " "),
+ "cache_files": android.JoinPathsWithPrefix(cacheFiles, " "),
+ "use_new_storage": strconv.FormatBool(newStorage),
+ "use_new_exported": strconv.FormatBool(newExported),
},
})
ctx.Phony("exported_java_aconfig_library", this.intermediatePath)
diff --git a/aconfig/init.go b/aconfig/init.go
index 21903e2..ab6ee46 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -91,7 +91,12 @@
Command: `rm -rf ${out}.tmp` +
`&& for cache in ${cache_files}; do ` +
` if [ -n "$$(${aconfig} dump-cache --dedup --cache $$cache --filter=is_exported:true --format='{fully_qualified_name}')" ]; then ` +
- ` ${aconfig} create-java-lib --cache $$cache --mode=exported --out ${out}.tmp; ` +
+ ` ${aconfig} create-java-lib` +
+ ` --cache $$cache` +
+ ` --mode=exported` +
+ ` --allow-instrumentation ${use_new_storage}` +
+ ` --new-exported ${use_new_exported}` +
+ ` --out ${out}.tmp; ` +
` fi ` +
`done` +
`&& $soong_zip -write_if_changed -jar -o ${out} -C ${out}.tmp -D ${out}.tmp` +
@@ -100,7 +105,7 @@
"$aconfig",
"$soong_zip",
},
- }, "cache_files")
+ }, "cache_files", "use_new_storage", "use_new_exported")
)
func init() {
diff --git a/android/Android.bp b/android/Android.bp
index 4e2006d..bb16856 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -98,6 +98,7 @@
"raw_files.go",
"recovery_build_prop.go",
"register.go",
+ "removed_package.go",
"rule_builder.go",
"sandbox.go",
"sbom.go",
diff --git a/android/defs.go b/android/defs.go
index c4e3b99..9f3fb1e 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -102,18 +102,6 @@
Description: "concatenate files to $out",
})
- // Calculates the hash of a directory and writes to a file.
- // Note that the directory to calculate the hash is intentionally not listed as an input,
- // to prevent adding directory as a ninja dependency. Thus, an implicit dependency to a file
- // is required.
- WriteDirectoryHash = pctx.AndroidStaticRule("WriteDirectoryHash",
- blueprint.RuleParams{
- Command: "rm -f $out && ${calculateDirectoryHash} $dir $out",
- CommandDeps: []string{"${calculateDirectoryHash}"},
- Description: "Calculates the hash of a directory and writes to $out",
- }, "dir",
- )
-
// Used only when USE_GOMA=true is set, to restrict non-goma jobs to the local parallelism value
localPool = blueprint.NewBuiltinPool("local_pool")
@@ -130,6 +118,4 @@
pctx.VariableFunc("RBEWrapper", func(ctx PackageVarContext) string {
return ctx.Config().RBEWrapper()
})
-
- pctx.HostBinToolVariable("calculateDirectoryHash", "calculate_directory_hash")
}
diff --git a/android/module.go b/android/module.go
index 72dba04..b8f2cae 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1049,7 +1049,7 @@
hostTargets = append(hostTargets, ctx.Config().BuildOSCommonTarget)
if ctx.Device() {
- for _, depName := range ctx.Module().RequiredModuleNames(ctx) {
+ for _, depName := range append(ctx.Module().RequiredModuleNames(ctx), ctx.Module().VintfFragmentModuleNames(ctx)...) {
for _, target := range deviceTargets {
addDep(target, depName)
}
@@ -1062,7 +1062,7 @@
}
if ctx.Host() {
- for _, depName := range ctx.Module().RequiredModuleNames(ctx) {
+ for _, depName := range append(ctx.Module().RequiredModuleNames(ctx), ctx.Module().VintfFragmentModuleNames(ctx)...) {
for _, target := range hostTargets {
// When a host module requires another host module, don't make a
// dependency if they have different OSes (i.e. hostcross).
diff --git a/android/recovery_build_prop.go b/android/recovery_build_prop.go
index 91d1904..ac7d2ec 100644
--- a/android/recovery_build_prop.go
+++ b/android/recovery_build_prop.go
@@ -83,8 +83,10 @@
r.properties.Product_build_prop,
r.properties.System_ext_build_prop,
} {
- if buildPropPath := PathForModuleSrc(ctx, proptools.String(buildProp)); buildPropPath != nil {
- buildProps = append(buildProps, buildPropPath)
+ if buildProp != nil {
+ if buildPropPath := PathForModuleSrc(ctx, proptools.String(buildProp)); buildPropPath != nil {
+ buildProps = append(buildProps, buildPropPath)
+ }
}
}
return buildProps
diff --git a/android/removed_package.go b/android/removed_package.go
new file mode 100644
index 0000000..aa54c2a
--- /dev/null
+++ b/android/removed_package.go
@@ -0,0 +1,60 @@
+package android
+
+import (
+ "fmt"
+
+ "github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
+)
+
+func init() {
+ InitRegistrationContext.RegisterModuleType("removed_package", removedPackageModuleFactory)
+}
+
+type removedPackageModuleProps struct {
+ // The error message to display when this module is built. This is optional, there is a
+ // reasonable default message.
+ Message *string
+}
+
+type removedPackageModule struct {
+ ModuleBase
+ properties removedPackageModuleProps
+}
+
+// removed_package will cause a build failure when it's included in PRODUCT_PACKAGES. It's needed
+// because currently you can add non-existent packages to PRODUCT_PACKAGES, and the build will
+// not notice/complain, unless you opt-into enforcement via $(call enforce-product-packages-exist).
+// Opting into the enforcement is difficult in some cases, because a package exists on some source
+// trees but not on others. removed_package is an intermediate solution that allows you to remove
+// a package and still get an error if it remains in PRODUCT_PACKAGES somewhere.
+func removedPackageModuleFactory() Module {
+ m := &removedPackageModule{}
+ InitAndroidModule(m)
+ m.AddProperties(&m.properties)
+ return m
+}
+
+var removedPackageRule = pctx.AndroidStaticRule("removed_package", blueprint.RuleParams{
+ Command: "echo $message && false",
+}, "message")
+
+func (m *removedPackageModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+ // Unchecked module so that checkbuild doesn't fail
+ ctx.UncheckedModule()
+
+ out := PathForModuleOut(ctx, "out.txt")
+ message := fmt.Sprintf("%s has been removed, and can no longer be used.", ctx.ModuleName())
+ if m.properties.Message != nil {
+ message = *m.properties.Message
+ }
+ ctx.Build(pctx, BuildParams{
+ Rule: removedPackageRule,
+ Output: out,
+ Args: map[string]string{
+ "message": proptools.ShellEscape(message),
+ },
+ })
+
+ ctx.InstallFile(PathForModuleInstall(ctx, "removed_module"), ctx.ModuleName(), out)
+}
diff --git a/android/variable.go b/android/variable.go
index 3cf66ea..a60cad5 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -532,7 +532,8 @@
OdmPropFiles []string `json:",omitempty"`
VendorPropFiles []string `json:",omitempty"`
- EnableUffdGc *string `json:",omitempty"`
+ EnableUffdGc *string `json:",omitempty"`
+ BoardKernelVersion *string `json:",omitempty"`
BoardAvbEnable *bool `json:",omitempty"`
BoardAvbSystemAddHashtreeFooterArgs []string `json:",omitempty"`
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index e3804e5..ee23b51 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -718,6 +718,16 @@
} else if global.EnableUffdGc == "default" {
// Generated by `build/make/core/Makefile`.
kernelVersionFile := android.PathForOutput(ctx, "dexpreopt/kernel_version_for_uffd_gc.txt")
+ if !ctx.Config().KatiEnabled() {
+ // In soong-only mode, we need to generate the kernel_version_for_uffd_gc.txt with kernel version
+ kernelVersion := android.String(ctx.Config().ProductVariables().BoardKernelVersion)
+ if kernelVersion == "" {
+ // https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=5382;drc=66783fca85911af9da48d9b4f35a61b3873023e9
+ panic("BOARD_KERNEL_VERSION is not set. Build team will convert more stuff from Make to Soong to support this scenario.")
+ }
+ android.WriteFileRule(ctx, kernelVersionFile, kernelVersion)
+ }
+
// Determine the UFFD GC flag by the kernel version file.
rule := android.NewRuleBuilder(pctx, ctx)
rule.Command().
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 7b0f51f..1f188d5 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -15,9 +15,10 @@
package dexpreopt
import (
- "android/soong/android"
"fmt"
"testing"
+
+ "android/soong/android"
)
func testSystemModuleConfig(ctx android.PathContext, name string) *ModuleConfig {
@@ -403,6 +404,7 @@
preparers := android.GroupFixturePreparers(
PrepareForTestWithFakeDex2oatd,
PrepareForTestWithDexpreoptConfig,
+ android.FixtureModifyConfig(android.SetKatiEnabledForTests),
FixtureSetEnableUffdGc("default"),
)
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
index ab1b96e..ea46556 100644
--- a/filesystem/android_device.go
+++ b/filesystem/android_device.go
@@ -79,5 +79,39 @@
}
func (a *androidDevice) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ a.buildTargetFilesZip(ctx)
+}
+func (a *androidDevice) buildTargetFilesZip(ctx android.ModuleContext) {
+ targetFilesDir := android.PathForModuleOut(ctx, "target_files_dir")
+ targetFilesZip := android.PathForModuleOut(ctx, "target_files.zip")
+
+ builder := android.NewRuleBuilder(pctx, ctx)
+ builder.Command().Textf("rm -rf %s", targetFilesDir.String())
+ builder.Command().Textf("mkdir -p %s", targetFilesDir.String())
+ if a.partitionProps.Vendor_partition_name != nil {
+ fsInfo := a.getFilesystemInfo(ctx, *a.partitionProps.Vendor_partition_name)
+ builder.Command().Textf("mkdir -p %s/VENDOR", targetFilesDir.String())
+ builder.Command().
+ BuiltTool("acp").
+ Textf("-rd %s/. %s/VENDOR", fsInfo.RootDir, targetFilesDir).
+ Implicit(fsInfo.Output) // so that the staging dir is built
+ }
+ builder.Command().
+ BuiltTool("soong_zip").
+ Text("-d").
+ FlagWithOutput("-o ", targetFilesZip).
+ FlagWithArg("-C ", targetFilesDir.String()).
+ FlagWithArg("-D ", targetFilesDir.String()).
+ Text("-sha256")
+ builder.Build("target_files_"+ctx.ModuleName(), "Build target_files.zip")
+}
+
+func (a *androidDevice) getFilesystemInfo(ctx android.ModuleContext, depName string) FilesystemInfo {
+ fsMod := ctx.GetDirectDepWithTag(depName, filesystemDepTag)
+ fsInfo, ok := android.OtherModuleProvider(ctx, fsMod, FilesystemProvider)
+ if !ok {
+ ctx.ModuleErrorf("Expected dependency %s to be a filesystem", depName)
+ }
+ return fsInfo
}
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 3f08648..5845d59 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -345,15 +345,14 @@
}
type FilesystemInfo struct {
+ // The built filesystem image
+ Output android.Path
// A text file containing the list of paths installed on the partition.
FileListFile android.Path
-
- // Root directory of the installed partition
- Rootdir android.Path
-
- // A text file containing the hash value of the metadata and the content hashes
- // of Rootdir
- DirectoryHashFile android.Path
+ // The root staging directory used to build the output filesystem. If consuming this, make sure
+ // to add a dependency on the Output file, as you cannot add dependencies on directories
+ // in ninja.
+ RootDir android.Path
}
var FilesystemProvider = blueprint.NewProvider[FilesystemInfo]()
@@ -452,25 +451,14 @@
rootDir = rootDir.Join(ctx, "root")
}
- rootDirHash := android.PathForModuleOut(ctx, "rootdir-hash.txt")
- ctx.Build(pctx, android.BuildParams{
- Rule: android.WriteDirectoryHash,
- Output: rootDirHash,
- Implicit: f.output,
- Args: map[string]string{
- "dir": rootDir.String(),
- },
- })
-
fileListFile := android.PathForModuleOut(ctx, "fileList")
android.WriteFileRule(ctx, fileListFile, f.installedFilesList())
android.SetProvider(ctx, FilesystemProvider, FilesystemInfo{
- FileListFile: fileListFile,
- Rootdir: rootDir,
- DirectoryHashFile: rootDirHash,
+ Output: f.output,
+ FileListFile: fileListFile,
+ RootDir: rootDir,
})
-
f.fileListFile = fileListFile
if proptools.Bool(f.properties.Unchecked_module) {
@@ -829,17 +817,17 @@
}
}
-func includeFilesRootDir(ctx android.ModuleContext) (rootDirs android.Paths, hashFiles android.Paths) {
+func includeFilesRootDir(ctx android.ModuleContext) (rootDirs android.Paths, partitions android.Paths) {
ctx.VisitDirectDepsWithTag(interPartitionInstallDependencyTag, func(m android.Module) {
if fsProvider, ok := android.OtherModuleProvider(ctx, m, FilesystemProvider); ok {
- rootDirs = append(rootDirs, fsProvider.Rootdir)
- hashFiles = append(hashFiles, fsProvider.DirectoryHashFile)
+ rootDirs = append(rootDirs, fsProvider.RootDir)
+ partitions = append(partitions, fsProvider.Output)
} else {
ctx.PropertyErrorf("include_files_of", "only filesystem modules can be listed in "+
"include_files_of but %s is not a filesystem module", m.Name())
}
})
- return rootDirs, hashFiles
+ return rootDirs, partitions
}
func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) (android.Path, android.OutputPath) {
@@ -874,7 +862,7 @@
f.filesystemBuilder.BuildLinkerConfigFile(ctx, builder, rebasedDir)
f.copyFilesToProductOut(ctx, builder, rebasedDir)
- rootDirs, hashFiles := includeFilesRootDir(ctx)
+ rootDirs, partitions := includeFilesRootDir(ctx)
output := android.PathForModuleOut(ctx, f.installFileName())
cmd := builder.Command().
@@ -884,7 +872,7 @@
for i := range len(rootDirs) {
cmd.Text(rootDirs[i].String())
}
- cmd.Implicits(hashFiles)
+ cmd.Implicits(partitions)
if nodeList := f.properties.Dev_nodes_description_file; nodeList != nil {
cmd.FlagWithInput("-n ", android.PathForModuleSrc(ctx, proptools.String(nodeList)))
diff --git a/fsgen/boot_imgs.go b/fsgen/boot_imgs.go
index 4e80720..c73c345 100644
--- a/fsgen/boot_imgs.go
+++ b/fsgen/boot_imgs.go
@@ -119,7 +119,6 @@
Avb_mode: avbInfo.avbMode,
Avb_private_key: avbInfo.avbkeyFilegroup,
Avb_rollback_index: avbInfo.avbRollbackIndex,
- Avb_algorithm: avbInfo.avbAlgorithm,
Dtb_prebuilt: dtbPrebuilt,
Cmdline: cmdline,
Bootconfig: vendorBootConfigImg,
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index 6474397..a2840b5 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -339,22 +339,7 @@
}
case "recovery":
dirs := append(commonPartitionDirs, []string{
- "odm_file_contexts",
- "odm_property_contexts",
- "plat_file_contexts",
- "plat_property_contexts",
- "plat_service_contexts",
- "product_file_contexts",
- "product_property_contexts",
- "product_service_contexts",
"sdcard",
- "sepolicy",
- "system_ext_file_contexts",
- "system_ext_property_contexts",
- "system_ext_service_contexts",
- "vendor_file_contexts",
- "vendor_property_contexts",
- "vendor_service_contexts",
}...)
dirsWithRoot := make([]string, len(dirs))
diff --git a/fsgen/fsgen_mutators.go b/fsgen/fsgen_mutators.go
index de0a1cb..9327669 100644
--- a/fsgen/fsgen_mutators.go
+++ b/fsgen/fsgen_mutators.go
@@ -150,7 +150,23 @@
},
"ramdisk": {},
"vendor_ramdisk": {},
- "recovery": {},
+ "recovery": {
+ "sepolicy.recovery": defaultDepCandidateProps(ctx.Config()),
+ "plat_file_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "plat_service_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "plat_property_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "system_ext_file_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "system_ext_service_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "system_ext_property_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "vendor_file_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "vendor_service_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "vendor_property_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "odm_file_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "odm_property_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "product_file_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "product_service_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ "product_property_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+ },
},
fsDepsMutex: sync.Mutex{},
moduleToInstallationProps: map[string]installationProperties{},
diff --git a/java/robolectric.go b/java/robolectric.go
index 5f46267..3b2c656 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -41,6 +41,7 @@
}
const robolectricCurrentLib = "Robolectric_all-target"
+const clearcutJunitLib = "ClearcutJunitListenerAar"
const robolectricPrebuiltLibPattern = "platform-robolectric-%s-prebuilt"
var (
@@ -106,6 +107,8 @@
ctx.PropertyErrorf("instrumentation_for", "missing required instrumented module")
}
+ ctx.AddVariationDependencies(nil, staticLibTag, clearcutJunitLib)
+
if v := String(r.robolectricProperties.Robolectric_prebuilt_version); v != "" {
ctx.AddVariationDependencies(nil, staticLibTag, fmt.Sprintf(robolectricPrebuiltLibPattern, v))
} else if !proptools.BoolDefault(r.robolectricProperties.Strict_mode, true) {
diff --git a/java/robolectric_test.go b/java/robolectric_test.go
index 4775bac..a2474e8 100644
--- a/java/robolectric_test.go
+++ b/java/robolectric_test.go
@@ -44,6 +44,12 @@
java_library {
name: "junitxml",
srcs: ["JUnitXml.java"]
+
+ }
+
+ java_library {
+ name: "ClearcutJunitListenerAar",
+ srcs: ["Runtime.java"]
}
java_library_host {
diff --git a/kernel/prebuilt_kernel_modules.go b/kernel/prebuilt_kernel_modules.go
index ec7a971..1225da0 100644
--- a/kernel/prebuilt_kernel_modules.go
+++ b/kernel/prebuilt_kernel_modules.go
@@ -247,6 +247,8 @@
return modulesDir.Join(ctx, "vendor", "lib", "modules")
} else if ctx.InstallInOdmDlkm() {
return modulesDir.Join(ctx, "odm", "lib", "modules")
+ } else if ctx.InstallInVendorRamdisk() {
+ return modulesDir.Join(ctx, "lib", "modules")
} else {
// not an android dlkm module.
return modulesDir
@@ -309,8 +311,8 @@
builder.Build("depmod", fmt.Sprintf("depmod %s", ctx.ModuleName()))
finalModulesDep := modulesDep
- // Add a leading slash to paths in modules.dep of android dlkm
- if ctx.InstallInSystemDlkm() || ctx.InstallInVendorDlkm() || ctx.InstallInOdmDlkm() {
+ // Add a leading slash to paths in modules.dep of android dlkm and vendor ramdisk
+ if ctx.InstallInSystemDlkm() || ctx.InstallInVendorDlkm() || ctx.InstallInOdmDlkm() || ctx.InstallInVendorRamdisk() {
finalModulesDep = modulesDep.ReplaceExtension(ctx, "intermediates")
ctx.Build(pctx, android.BuildParams{
Rule: addLeadingSlashToPaths,
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 49e6b73..d39c84a 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -327,11 +327,3 @@
"rustc_linker.py",
],
}
-
-python_binary_host {
- name: "calculate_directory_hash",
- main: "calculate_directory_hash.py",
- srcs: [
- "calculate_directory_hash.py",
- ],
-}
diff --git a/scripts/calculate_directory_hash.py b/scripts/calculate_directory_hash.py
deleted file mode 100755
index d4802d8..0000000
--- a/scripts/calculate_directory_hash.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2024 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-"""A tool for calculating the hash of a directory based on file contents and metadata."""
-
-import argparse
-import hashlib
-import os
-import stat
-
-def calculate_hash(directory: str) -> str:
- """
- Calculates the hash of a directory, including file contents and metadata.
-
- Following informations are taken into consideration:
- * Name: The file or directory name.
- * File Type: Whether it's a regular file, directory, symbolic link, etc.
- * Size: The size of the file in bytes.
- * Permissions: The file's access permissions (read, write, execute).
- * Content Hash (for files): The SHA-1 hash of the file's content.
- """
-
- output = []
- for root, _, files in os.walk(directory):
- for file in files:
- filepath = os.path.join(root, file)
- file_stat = os.lstat(filepath)
- stat_info = f"{filepath} {stat.filemode(file_stat.st_mode)} {file_stat.st_size}"
-
- if os.path.islink(filepath):
- stat_info += os.readlink(filepath)
- elif os.path.isfile(filepath):
- with open(filepath, "rb") as f:
- file_hash = hashlib.sha1(f.read()).hexdigest()
- stat_info += f" {file_hash}"
-
- output.append(stat_info)
-
- return hashlib.sha1("\n".join(sorted(output)).encode()).hexdigest()
-
-if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="Calculate the hash of a directory.")
- parser.add_argument("directory", help="Path to the directory")
- parser.add_argument("output_file", help="Path to the output file")
- args = parser.parse_args()
-
- hash_value = calculate_hash(args.directory)
- with open(args.output_file, "w") as f:
- f.write(hash_value)