Merge "Support OUT_DIR for license graph." am: bc8d61f17b am: 443a7b25e3

Original change: https://android-review.googlesource.com/c/platform/build/+/2036930

Change-Id: I305c5312b687e39f49fc6d486f15faf97c87e3db
diff --git a/core/Makefile b/core/Makefile
index e1128bc..826623b 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -1477,7 +1477,7 @@
 $(1): PRIVATE_PRODUCT := $(2)
 $(1): PRIVATE_MESSAGE := $(3)
 $(1): $(call corresponding-license-metadata,$(4)) $(XMLNOTICE) $(BUILD_SYSTEM)/Makefile
-	$(XMLNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $(call corresponding-license-metadata,$(4))
+	OUT_DIR=$(OUT_DIR) $(XMLNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $(call corresponding-license-metadata,$(4))
 
 notice_files: $(1)
 endef
@@ -1493,7 +1493,7 @@
 $(1): PRIVATE_PRODUCT := $(2)
 $(1): PRIVATE_MESSAGE := $(3)
 $(1): $(call corresponding-license-metadata,$(4)) $(TEXTNOTICE) $(BUILD_SYSTEM)/Makefile
-	$(TEXTNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $(call corresponding-license-metadata,$(4))
+	OUT_DIR=$(OUT_DIR) $(TEXTNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $(call corresponding-license-metadata,$(4))
 
 notice_files: $(1)
 endef
@@ -1509,7 +1509,7 @@
 $(1): PRIVATE_PRODUCT := $(2)
 $(1): PRIVATE_MESSAGE := $(3)
 $(1): $(call corresponding-license-metadata,$(4)) $(HTMLNOTICE) $(BUILD_SYSTEM)/Makefile
-	$(HTMLNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $(call corresponding-license-metadata,$(4))
+	OUT_DIR=$(OUT_DIR) $(HTMLNOTICE) -o $$@ -product=$$(PRIVATE_PRODUCT) -title=$$(PRIVATE_MESSAGE) $(foreach prefix, $(5), -strip_prefix=$(prefix)) $(call corresponding-license-metadata,$(4))
 
 notice_files: $(1)
 endef
diff --git a/core/definitions.mk b/core/definitions.mk
index 0a6a773..2711f57 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -579,14 +579,14 @@
     $(ALL_MODULES.$(target).META_LIC), \
     $(if $(strip $(ALL_TARGETS.$(target).META_LIC)), \
       $(ALL_TARGETS.$(target).META_LIC), \
-      $(call license-metadata-dir)/$(target).meta_lic))))
+      $(call append-path,$(call license-metadata-dir),$(patsubst $(OUT_DIR)%,out%,$(target).meta_lic))))))
 endef
 
 ###########################################################
 ## License metadata build rule for my_register_name $(1)
 ###########################################################
 define license-metadata-rule
-$(foreach meta_lic, $(ALL_MODULES.$(1).DELAYED_META_LIC),$(call _license-metadata-rule,$(1),$(meta_lic)))
+$(foreach meta_lic, $(subst //,/,$(ALL_MODULES.$(1).DELAYED_META_LIC)),$(call _license-metadata-rule,$(1),$(meta_lic)))
 $(call notice-rule,$(1))
 endef
 
@@ -626,7 +626,7 @@
 $(2) : $(foreach d,$(_deps),$(call word-colon,1,$(d))) $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
 	rm -f $$@
 	mkdir -p $$(dir $$@)
-	$(BUILD_LICENSE_METADATA) \
+	OUT_DIR=$(OUT_DIR) $(BUILD_LICENSE_METADATA) \
 	  $$(addprefix -mt ,$$(PRIVATE_MODULE_TYPE)) \
 	  $$(addprefix -mc ,$$(PRIVATE_MODULE_CLASS)) \
 	  $$(addprefix -k ,$$(PRIVATE_KINDS)) \
@@ -670,6 +670,7 @@
 define non-module-license-metadata-rule
 $(strip $(eval _dir := $(call license-metadata-dir)))
 $(strip $(eval _tgt := $(strip $(1))))
+$(strip $(eval _meta := $(call append-path,$(_dir),$(patsubst $(OUT_DIR)%,out%,$(_tgt).meta_lic))))
 $(strip $(eval _deps := $(sort $(filter-out 0p: :,$(foreach d,$(strip $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)),$(ALL_TARGETS.$(call word-colon,1,$(d)).META_LIC):$(call wordlist-colon,2,9999,$(d)))))))
 $(strip $(eval _notices := $(sort $(ALL_NON_MODULES.$(_tgt).NOTICES))))
 $(strip $(eval _path := $(sort $(ALL_NON_MODULES.$(_tgt).PATH))))
@@ -682,21 +683,21 @@
   )) \
 )
 
-$(_dir)/$(_tgt).meta_lic: PRIVATE_KINDS := $(sort $(ALL_NON_MODULES.$(_tgt).LICENSE_KINDS))
-$(_dir)/$(_tgt).meta_lic: PRIVATE_CONDITIONS := $(sort $(ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS))
-$(_dir)/$(_tgt).meta_lic: PRIVATE_NOTICES := $(_notices)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_NOTICE_DEPS := $(_deps)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_SOURCES := $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_TARGETS := $(_tgt)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_PATH := $(_path)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_IS_CONTAINER := $(ALL_NON_MODULES.$(_tgt).IS_CONTAINER)
-$(_dir)/$(_tgt).meta_lic: PRIVATE_PACKAGE_NAME := $(strip $(ALL_NON_MODULES.$(_tgt).LICENSE_PACKAGE_NAME))
-$(_dir)/$(_tgt).meta_lic: PRIVATE_INSTALL_MAP := $(strip $(_install_map))
-$(_dir)/$(_tgt).meta_lic: $(BUILD_LICENSE_METADATA)
-$(_dir)/$(_tgt).meta_lic : $(foreach d,$(_deps),$(call word-colon,1,$(d))) $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
+$(_meta): PRIVATE_KINDS := $(sort $(ALL_NON_MODULES.$(_tgt).LICENSE_KINDS))
+$(_meta): PRIVATE_CONDITIONS := $(sort $(ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS))
+$(_meta): PRIVATE_NOTICES := $(_notices)
+$(_meta): PRIVATE_NOTICE_DEPS := $(_deps)
+$(_meta): PRIVATE_SOURCES := $(ALL_NON_MODULES.$(_tgt).DEPENDENCIES)
+$(_meta): PRIVATE_TARGETS := $(_tgt)
+$(_meta): PRIVATE_PATH := $(_path)
+$(_meta): PRIVATE_IS_CONTAINER := $(ALL_NON_MODULES.$(_tgt).IS_CONTAINER)
+$(_meta): PRIVATE_PACKAGE_NAME := $(strip $(ALL_NON_MODULES.$(_tgt).LICENSE_PACKAGE_NAME))
+$(_meta): PRIVATE_INSTALL_MAP := $(strip $(_install_map))
+$(_meta): $(BUILD_LICENSE_METADATA)
+$(_meta) : $(foreach d,$(_deps),$(call word-colon,1,$(d))) $(foreach n,$(_notices),$(call word-colon,1,$(n)) )
 	rm -f $$@
 	mkdir -p $$(dir $$@)
-	$(BUILD_LICENSE_METADATA) \
+	OUT_DIR=$(OUT_DIR) $(BUILD_LICENSE_METADATA) \
           -mt raw -mc unknown \
 	  $$(addprefix -k ,$$(PRIVATE_KINDS)) \
 	  $$(addprefix -c ,$$(PRIVATE_CONDITIONS)) \
@@ -723,7 +724,7 @@
 ###########################################################
 define declare-license-metadata
 $(strip \
-  $(eval _tgt := $(strip $(1))) \
+  $(eval _tgt := $(subst //,/,$(strip $(1)))) \
   $(eval ALL_NON_MODULES += $(_tgt)) \
   $(eval ALL_NON_MODULES.$(_tgt).LICENSE_KINDS := $(strip $(2))) \
   $(eval ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS := $(strip $(3))) \
@@ -763,7 +764,7 @@
 ###########################################################
 define declare-container-license-metadata
 $(strip \
-  $(eval _tgt := $(strip $(1))) \
+  $(eval _tgt := $(subst //,/,$(strip $(1)))) \
   $(eval ALL_NON_MODULES += $(_tgt)) \
   $(eval ALL_NON_MODULES.$(_tgt).LICENSE_KINDS := $(strip $(2))) \
   $(eval ALL_NON_MODULES.$(_tgt).LICENSE_CONDITIONS := $(strip $(3))) \
@@ -781,7 +782,7 @@
 ###########################################################
 define declare-0p-target
 $(strip \
-  $(eval _tgt := $(strip $(1))) \
+  $(eval _tgt := $(subst //,/,$(strip $(1)))) \
   $(eval ALL_0P_TARGETS += $(_tgt)) \
 )
 endef
@@ -898,7 +899,7 @@
 	$(hide) rm -f $$(PRIVATE_LIST_FILE)
 	$(hide) mkdir -p $$(dir $$(PRIVATE_LIST_FILE))
 	$(hide) find out -name '*meta_lic' -type f -printf '"%p"\n' >$$(PRIVATE_LIST_FILE)
-	$(COMPLIANCENOTICE_SHIPPEDLIBS) @$$(PRIVATE_LIST_FILE)
+	OUT_DIR=$(OUT_DIR) $(COMPLIANCENOTICE_SHIPPEDLIBS) @$$(PRIVATE_LIST_FILE)
 endef
 
 ###########################################################
@@ -923,7 +924,7 @@
     $(eval ALL_TARGETS.$(t).META_LIC := 0p) \
   ) \
   $(foreach t,$(sort $(ALL_NON_MODULES)), \
-    $(eval ALL_TARGETS.$(t).META_LIC := $(_dir)/$(t).meta_lic) \
+    $(eval ALL_TARGETS.$(t).META_LIC := $(call append-path,$(_dir),$(patsubst $(OUT_DIR)%,out%,$(t).meta_lic))) \
   ) \
   $(foreach t,$(sort $(ALL_NON_MODULES)),$(eval $(call non-module-license-metadata-rule,$(t)))) \
   $(foreach m,$(sort $(ALL_MODULES)),$(eval $(call license-metadata-rule,$(m)))) \
diff --git a/tools/compliance/cmd/bom/bom.go b/tools/compliance/cmd/bom/bom.go
index 5363a59..b613a1f 100644
--- a/tools/compliance/cmd/bom/bom.go
+++ b/tools/compliance/cmd/bom/bom.go
@@ -116,7 +116,7 @@
 		ofile = &bytes.Buffer{}
 	}
 
-	ctx := &context{ofile, os.Stderr, os.DirFS("."), *stripPrefix}
+	ctx := &context{ofile, os.Stderr, compliance.FS, *stripPrefix}
 
 	err := billOfMaterials(ctx, flag.Args()...)
 	if err != nil {
diff --git a/tools/compliance/cmd/bom/bom_test.go b/tools/compliance/cmd/bom/bom_test.go
index 4a9889f..87a3b50 100644
--- a/tools/compliance/cmd/bom/bom_test.go
+++ b/tools/compliance/cmd/bom/bom_test.go
@@ -21,6 +21,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -37,6 +39,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		stripPrefix string
 		expectedOut []string
@@ -282,7 +285,7 @@
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
 
-			ctx := context{stdout, stderr, os.DirFS("."), []string{tt.stripPrefix}}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), []string{tt.stripPrefix}}
 
 			err := billOfMaterials(&ctx, rootFiles...)
 			if err != nil {
diff --git a/tools/compliance/cmd/checkshare/checkshare.go b/tools/compliance/cmd/checkshare/checkshare.go
index 752d14b..73bdcb5 100644
--- a/tools/compliance/cmd/checkshare/checkshare.go
+++ b/tools/compliance/cmd/checkshare/checkshare.go
@@ -18,6 +18,7 @@
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"sort"
@@ -68,7 +69,7 @@
 		os.Exit(2)
 	}
 
-	err := checkShare(os.Stdout, os.Stderr, flag.Args()...)
+	err := checkShare(os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
 	if err != nil {
 		if err != failConflicts {
 			if err == failNoneRequested {
@@ -82,14 +83,14 @@
 }
 
 // checkShare implements the checkshare utility.
-func checkShare(stdout, stderr io.Writer, files ...string) error {
+func checkShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
 
 	if len(files) < 1 {
 		return failNoneRequested
 	}
 
 	// Read the license graph from the license metadata files (*.meta_lic).
-	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
 	if err != nil {
 		return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err)
 	}
diff --git a/tools/compliance/cmd/checkshare/checkshare_test.go b/tools/compliance/cmd/checkshare/checkshare_test.go
index c9b62e1..fdcab29 100644
--- a/tools/compliance/cmd/checkshare/checkshare_test.go
+++ b/tools/compliance/cmd/checkshare/checkshare_test.go
@@ -20,6 +20,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -56,6 +58,7 @@
 	tests := []struct {
 		condition        string
 		name             string
+		outDir           string
 		roots            []string
 		expectedStdout   string
 		expectedOutcomes outcomeList
@@ -248,7 +251,7 @@
 			for _, r := range tt.roots {
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
-			err := checkShare(stdout, stderr, rootFiles...)
+			err := checkShare(stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil && err != failConflicts {
 				t.Fatalf("checkshare: error = %v, stderr = %v", err, stderr)
 				return
diff --git a/tools/compliance/cmd/dumpgraph/dumpgraph.go b/tools/compliance/cmd/dumpgraph/dumpgraph.go
index fa16b1b..32a3fc4 100644
--- a/tools/compliance/cmd/dumpgraph/dumpgraph.go
+++ b/tools/compliance/cmd/dumpgraph/dumpgraph.go
@@ -18,6 +18,7 @@
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"sort"
@@ -97,7 +98,7 @@
 
 	ctx := &context{*graphViz, *labelConditions, *stripPrefix}
 
-	err := dumpGraph(ctx, os.Stdout, os.Stderr, flag.Args()...)
+	err := dumpGraph(ctx, os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
 	if err != nil {
 		if err == failNoneRequested {
 			flag.Usage()
@@ -109,13 +110,13 @@
 }
 
 // dumpGraph implements the dumpgraph utility.
-func dumpGraph(ctx *context, stdout, stderr io.Writer, files ...string) error {
+func dumpGraph(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
 	if len(files) < 1 {
 		return failNoneRequested
 	}
 
 	// Read the license graph from the license metadata files (*.meta_lic).
-	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
 	if err != nil {
 		return fmt.Errorf("Unable to read license metadata file(s) %q: %w\n", files, err)
 	}
diff --git a/tools/compliance/cmd/dumpgraph/dumpgraph_test.go b/tools/compliance/cmd/dumpgraph/dumpgraph_test.go
index 67b2b40..d1deed3 100644
--- a/tools/compliance/cmd/dumpgraph/dumpgraph_test.go
+++ b/tools/compliance/cmd/dumpgraph/dumpgraph_test.go
@@ -20,6 +20,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -36,6 +38,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		ctx         context
 		expectedOut []string
@@ -491,7 +494,7 @@
 			for _, r := range tt.roots {
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
-			err := dumpGraph(&tt.ctx, stdout, stderr, rootFiles...)
+			err := dumpGraph(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil {
 				t.Fatalf("dumpgraph: error = %v, stderr = %v", err, stderr)
 				return
@@ -583,6 +586,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		ctx         context
 		expectedOut []getMatcher
@@ -1217,7 +1221,7 @@
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
 			tt.ctx.graphViz = true
-			err := dumpGraph(&tt.ctx, stdout, stderr, rootFiles...)
+			err := dumpGraph(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil {
 				t.Fatalf("dumpgraph: error = %v, stderr = %v", err, stderr)
 				return
diff --git a/tools/compliance/cmd/dumpresolutions/dumpresolutions.go b/tools/compliance/cmd/dumpresolutions/dumpresolutions.go
index 9c5e972..d02c238 100644
--- a/tools/compliance/cmd/dumpresolutions/dumpresolutions.go
+++ b/tools/compliance/cmd/dumpresolutions/dumpresolutions.go
@@ -18,6 +18,7 @@
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"sort"
@@ -110,7 +111,7 @@
 		labelConditions: *labelConditions,
 		stripPrefix:     *stripPrefix,
 	}
-	_, err := dumpResolutions(ctx, os.Stdout, os.Stderr, flag.Args()...)
+	_, err := dumpResolutions(ctx, os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
 	if err != nil {
 		if err == failNoneRequested {
 			flag.Usage()
@@ -122,13 +123,13 @@
 }
 
 // dumpResolutions implements the dumpresolutions utility.
-func dumpResolutions(ctx *context, stdout, stderr io.Writer, files ...string) (*compliance.LicenseGraph, error) {
+func dumpResolutions(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) (*compliance.LicenseGraph, error) {
 	if len(files) < 1 {
 		return nil, failNoneRequested
 	}
 
 	// Read the license graph from the license metadata files (*.meta_lic).
-	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
 	if err != nil {
 		return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
 	}
diff --git a/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go b/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go
index 6fe1e8a..63fd157 100644
--- a/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go
+++ b/tools/compliance/cmd/dumpresolutions/dumpresolutions_test.go
@@ -38,6 +38,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		ctx         context
 		expectedOut []string
@@ -902,7 +903,7 @@
 			for _, r := range tt.roots {
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
-			_, err := dumpResolutions(&tt.ctx, stdout, stderr, rootFiles...)
+			_, err := dumpResolutions(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil {
 				t.Fatalf("dumpresolutions: error = %v, stderr = %v", err, stderr)
 				return
@@ -1011,6 +1012,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		ctx         context
 		expectedOut []getMatcher
@@ -3298,7 +3300,7 @@
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
 			tt.ctx.graphViz = true
-			lg, err := dumpResolutions(&tt.ctx, stdout, stderr, rootFiles...)
+			lg, err := dumpResolutions(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil {
 				t.Fatalf("dumpresolutions: error = %v, stderr = %v", err, stderr)
 				return
diff --git a/tools/compliance/cmd/htmlnotice/htmlnotice.go b/tools/compliance/cmd/htmlnotice/htmlnotice.go
index ffb0585..e98b272 100644
--- a/tools/compliance/cmd/htmlnotice/htmlnotice.go
+++ b/tools/compliance/cmd/htmlnotice/htmlnotice.go
@@ -141,7 +141,7 @@
 
 	var deps []string
 
-	ctx := &context{ofile, os.Stderr, os.DirFS("."), *includeTOC, *product, *stripPrefix, *title, &deps}
+	ctx := &context{ofile, os.Stderr, compliance.FS, *includeTOC, *product, *stripPrefix, *title, &deps}
 
 	err := htmlNotice(ctx, flag.Args()...)
 	if err != nil {
diff --git a/tools/compliance/cmd/htmlnotice/htmlnotice_test.go b/tools/compliance/cmd/htmlnotice/htmlnotice_test.go
index 1b01d16..b927018 100644
--- a/tools/compliance/cmd/htmlnotice/htmlnotice_test.go
+++ b/tools/compliance/cmd/htmlnotice/htmlnotice_test.go
@@ -24,6 +24,8 @@
 	"regexp"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 var (
@@ -54,6 +56,7 @@
 	tests := []struct {
 		condition    string
 		name         string
+		outDir       string
 		roots        []string
 		includeTOC   bool
 		stripPrefix  string
@@ -651,7 +654,7 @@
 
 			var deps []string
 
-			ctx := context{stdout, stderr, os.DirFS("."), tt.includeTOC, "", []string{tt.stripPrefix}, tt.title, &deps}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), tt.includeTOC, "", []string{tt.stripPrefix}, tt.title, &deps}
 
 			err := htmlNotice(&ctx, rootFiles...)
 			if err != nil {
diff --git a/tools/compliance/cmd/listshare/listshare.go b/tools/compliance/cmd/listshare/listshare.go
index 030caa7..7f4038b 100644
--- a/tools/compliance/cmd/listshare/listshare.go
+++ b/tools/compliance/cmd/listshare/listshare.go
@@ -18,6 +18,7 @@
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"sort"
@@ -54,7 +55,7 @@
 		os.Exit(2)
 	}
 
-	err := listShare(os.Stdout, os.Stderr, flag.Args()...)
+	err := listShare(os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
 	if err != nil {
 		if err == failNoneRequested {
 			flag.Usage()
@@ -66,14 +67,14 @@
 }
 
 // listShare implements the listshare utility.
-func listShare(stdout, stderr io.Writer, files ...string) error {
+func listShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
 	// Must be at least one root file.
 	if len(files) < 1 {
 		return failNoneRequested
 	}
 
 	// Read the license graph from the license metadata files (*.meta_lic).
-	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
 	if err != nil {
 		return fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
 	}
diff --git a/tools/compliance/cmd/listshare/listshare_test.go b/tools/compliance/cmd/listshare/listshare_test.go
index 91e9a43..c1e38be 100644
--- a/tools/compliance/cmd/listshare/listshare_test.go
+++ b/tools/compliance/cmd/listshare/listshare_test.go
@@ -20,6 +20,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -40,6 +42,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		expectedOut []projectShare
 	}{
@@ -481,7 +484,7 @@
 			for _, r := range tt.roots {
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
-			err := listShare(stdout, stderr, rootFiles...)
+			err := listShare(stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			if err != nil {
 				t.Fatalf("listshare: error = %v, stderr = %v", err, stderr)
 				return
diff --git a/tools/compliance/cmd/rtrace/rtrace.go b/tools/compliance/cmd/rtrace/rtrace.go
index 7c63979..91171c4 100644
--- a/tools/compliance/cmd/rtrace/rtrace.go
+++ b/tools/compliance/cmd/rtrace/rtrace.go
@@ -18,6 +18,7 @@
 	"flag"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"sort"
@@ -107,7 +108,7 @@
 		sources:         *sources,
 		stripPrefix:     *stripPrefix,
 	}
-	_, err := traceRestricted(ctx, os.Stdout, os.Stderr, flag.Args()...)
+	_, err := traceRestricted(ctx, os.Stdout, os.Stderr, compliance.FS, flag.Args()...)
 	if err != nil {
 		if err == failNoneRequested {
 			flag.Usage()
@@ -119,7 +120,7 @@
 }
 
 // traceRestricted implements the rtrace utility.
-func traceRestricted(ctx *context, stdout, stderr io.Writer, files ...string) (*compliance.LicenseGraph, error) {
+func traceRestricted(ctx *context, stdout, stderr io.Writer, rootFS fs.FS, files ...string) (*compliance.LicenseGraph, error) {
 	if len(files) < 1 {
 		return nil, failNoneRequested
 	}
@@ -129,7 +130,7 @@
 	}
 
 	// Read the license graph from the license metadata files (*.meta_lic).
-	licenseGraph, err := compliance.ReadLicenseGraph(os.DirFS("."), stderr, files)
+	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
 	if err != nil {
 		return nil, fmt.Errorf("Unable to read license metadata file(s) %q: %v\n", files, err)
 	}
diff --git a/tools/compliance/cmd/rtrace/rtrace_test.go b/tools/compliance/cmd/rtrace/rtrace_test.go
index a8eb884..cbe9461 100644
--- a/tools/compliance/cmd/rtrace/rtrace_test.go
+++ b/tools/compliance/cmd/rtrace/rtrace_test.go
@@ -20,6 +20,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -36,6 +38,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		ctx         context
 		expectedOut []string
@@ -289,7 +292,7 @@
 			if len(tt.ctx.sources) < 1 {
 				tt.ctx.sources = rootFiles
 			}
-			_, err := traceRestricted(&tt.ctx, stdout, stderr, rootFiles...)
+			_, err := traceRestricted(&tt.ctx, stdout, stderr, compliance.GetFS(tt.outDir), rootFiles...)
 			t.Logf("rtrace: stderr = %v", stderr)
 			t.Logf("rtrace: stdout = %v", stdout)
 			if err != nil {
diff --git a/tools/compliance/cmd/shippedlibs/shippedlibs.go b/tools/compliance/cmd/shippedlibs/shippedlibs.go
index 94b19f1..9d25dd3 100644
--- a/tools/compliance/cmd/shippedlibs/shippedlibs.go
+++ b/tools/compliance/cmd/shippedlibs/shippedlibs.go
@@ -117,7 +117,7 @@
 		ofile = &bytes.Buffer{}
 	}
 
-	ctx := &context{ofile, os.Stderr, os.DirFS(".")}
+	ctx := &context{ofile, os.Stderr, compliance.FS}
 
 	err = shippedLibs(ctx, flags.Args()...)
 	if err != nil {
diff --git a/tools/compliance/cmd/shippedlibs/shippedlibs_test.go b/tools/compliance/cmd/shippedlibs/shippedlibs_test.go
index b6aad6d..983747c 100644
--- a/tools/compliance/cmd/shippedlibs/shippedlibs_test.go
+++ b/tools/compliance/cmd/shippedlibs/shippedlibs_test.go
@@ -21,6 +21,8 @@
 	"os"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 func TestMain(m *testing.M) {
@@ -37,6 +39,7 @@
 	tests := []struct {
 		condition   string
 		name        string
+		outDir      string
 		roots       []string
 		expectedOut []string
 	}{
@@ -201,7 +204,7 @@
 				rootFiles = append(rootFiles, "testdata/"+tt.condition+"/"+r)
 			}
 
-			ctx := context{stdout, stderr, os.DirFS(".")}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir)}
 
 			err := shippedLibs(&ctx, rootFiles...)
 			if err != nil {
diff --git a/tools/compliance/cmd/textnotice/textnotice.go b/tools/compliance/cmd/textnotice/textnotice.go
index 58afb48..cfa0859 100644
--- a/tools/compliance/cmd/textnotice/textnotice.go
+++ b/tools/compliance/cmd/textnotice/textnotice.go
@@ -137,7 +137,7 @@
 
 	var deps []string
 
-	ctx := &context{ofile, os.Stderr, os.DirFS("."), *product, *stripPrefix, *title, &deps}
+	ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, *title, &deps}
 
 	err := textNotice(ctx, flag.Args()...)
 	if err != nil {
diff --git a/tools/compliance/cmd/textnotice/textnotice_test.go b/tools/compliance/cmd/textnotice/textnotice_test.go
index 9d8d0ca..e661a44 100644
--- a/tools/compliance/cmd/textnotice/textnotice_test.go
+++ b/tools/compliance/cmd/textnotice/textnotice_test.go
@@ -23,6 +23,8 @@
 	"regexp"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 var (
@@ -43,6 +45,7 @@
 	tests := []struct {
 		condition    string
 		name         string
+		outDir       string
 		roots        []string
 		stripPrefix  string
 		expectedOut  []matcher
@@ -564,7 +567,7 @@
 
 			var deps []string
 
-			ctx := context{stdout, stderr, os.DirFS("."), "", []string{tt.stripPrefix}, "", &deps}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, "", &deps}
 
 			err := textNotice(&ctx, rootFiles...)
 			if err != nil {
diff --git a/tools/compliance/cmd/xmlnotice/xmlnotice.go b/tools/compliance/cmd/xmlnotice/xmlnotice.go
index 1c712cb..84859d7 100644
--- a/tools/compliance/cmd/xmlnotice/xmlnotice.go
+++ b/tools/compliance/cmd/xmlnotice/xmlnotice.go
@@ -139,7 +139,7 @@
 
 	var deps []string
 
-	ctx := &context{ofile, os.Stderr, os.DirFS("."), *product, *stripPrefix, *title, &deps}
+	ctx := &context{ofile, os.Stderr, compliance.FS, *product, *stripPrefix, *title, &deps}
 
 	err := xmlNotice(ctx, flag.Args()...)
 	if err != nil {
diff --git a/tools/compliance/cmd/xmlnotice/xmlnotice_test.go b/tools/compliance/cmd/xmlnotice/xmlnotice_test.go
index 424c95e..731e783 100644
--- a/tools/compliance/cmd/xmlnotice/xmlnotice_test.go
+++ b/tools/compliance/cmd/xmlnotice/xmlnotice_test.go
@@ -24,6 +24,8 @@
 	"regexp"
 	"strings"
 	"testing"
+
+	"android/soong/tools/compliance"
 )
 
 var (
@@ -45,6 +47,7 @@
 	tests := []struct {
 		condition    string
 		name         string
+		outDir       string
 		roots        []string
 		stripPrefix  string
 		expectedOut  []matcher
@@ -459,7 +462,7 @@
 
 			var deps []string
 
-			ctx := context{stdout, stderr, os.DirFS("."), "", []string{tt.stripPrefix}, "", &deps}
+			ctx := context{stdout, stderr, compliance.GetFS(tt.outDir), "", []string{tt.stripPrefix}, "", &deps}
 
 			err := xmlNotice(&ctx, rootFiles...)
 			if err != nil {
diff --git a/tools/compliance/readgraph.go b/tools/compliance/readgraph.go
index 6f91e1c..7516440 100644
--- a/tools/compliance/readgraph.go
+++ b/tools/compliance/readgraph.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"io"
 	"io/fs"
+	"os"
 	"strings"
 	"sync"
 
@@ -31,6 +32,22 @@
 	ConcurrentReaders = 5
 )
 
+type globalFS struct{}
+
+func (s globalFS) Open(name string) (fs.File, error) {
+	return os.Open(name)
+}
+
+var FS globalFS
+
+// GetFS returns a filesystem for accessing files under the OUT_DIR environment variable.
+func GetFS(outDir string) fs.FS {
+	if len(outDir) > 0 {
+		return os.DirFS(outDir)
+	}
+	return os.DirFS(".")
+}
+
 // result describes the outcome of reading and parsing a single license metadata file.
 type result struct {
 	// file identifies the path to the license metadata file