diff --git a/Help/command/add_custom_command.rst b/Help/command/add_custom_command.rst
index 569f1e853b3709a5c536bb8d78f262adf6e9bc5d..a053e59f79268d90d154aa25f7d0a5abc27fdae8 100644
--- a/Help/command/add_custom_command.rst
+++ b/Help/command/add_custom_command.rst
@@ -338,6 +338,8 @@ will re-run whenever ``in.txt`` changes.
   where ``<config>`` is the build configuration, and then compile the generated
   source as part of a library.
 
+.. _`add_custom_command(TARGET)`:
+
 Build Events
 ^^^^^^^^^^^^
 
@@ -388,6 +390,9 @@ of the following is specified:
 
   This allows to add individual build events for every configuration.
 
+.. versionadded:: 3.21
+  Support for target-dependent generator expressions.
+
 Examples: Build Events
 ^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/Help/release/dev/add_custom_command-TARGET-genex.rst b/Help/release/dev/add_custom_command-TARGET-genex.rst
new file mode 100644
index 0000000000000000000000000000000000000000..86f8b36be56cfbbf110775712fa8d3ab094bb126
--- /dev/null
+++ b/Help/release/dev/add_custom_command-TARGET-genex.rst
@@ -0,0 +1,5 @@
+add_custom_command-TARGET-genex
+-------------------------------
+
+* The :ref:`add_custom_command(TARGET) <add_custom_command(TARGET)>` command
+  gained support for resolving target-dependent generator expressions.
diff --git a/Source/cmCustomCommand.cxx b/Source/cmCustomCommand.cxx
index f6b99898485da54b96a73f4b86a71f74891830f1..ec60ff7559ed96559a253c1145c9bf4226786538 100644
--- a/Source/cmCustomCommand.cxx
+++ b/Source/cmCustomCommand.cxx
@@ -150,3 +150,13 @@ void cmCustomCommand::SetCMP0116Status(cmPolicies::PolicyStatus cmp0116)
 {
   this->CMP0116Status = cmp0116;
 }
+
+const std::string& cmCustomCommand::GetTarget() const
+{
+  return this->Target;
+}
+
+void cmCustomCommand::SetTarget(const std::string& target)
+{
+  this->Target = target;
+}
diff --git a/Source/cmCustomCommand.h b/Source/cmCustomCommand.h
index e22c7a4064c8e426267b744e0a6b6b669ca0c36c..5cbd3d17a9e1273514753190381df01c2b0e29c4 100644
--- a/Source/cmCustomCommand.h
+++ b/Source/cmCustomCommand.h
@@ -100,6 +100,10 @@ public:
   cmPolicies::PolicyStatus GetCMP0116Status() const;
   void SetCMP0116Status(cmPolicies::PolicyStatus cmp0116);
 
+  /** Set/Get the associated target */
+  const std::string& GetTarget() const;
+  void SetTarget(const std::string& target);
+
 private:
   std::vector<std::string> Outputs;
   std::vector<std::string> Byproducts;
@@ -107,6 +111,7 @@ private:
   cmCustomCommandLines CommandLines;
   cmListFileBacktrace Backtrace;
   cmImplicitDependsList ImplicitDepends;
+  std::string Target;
   std::string Comment;
   std::string WorkingDirectory;
   std::string Depfile;
diff --git a/Source/cmCustomCommandGenerator.cxx b/Source/cmCustomCommandGenerator.cxx
index 1054beb375f769df13e3c488edf35a0243299446..77779ba781070ddb972181bb2faf21659a9046cc 100644
--- a/Source/cmCustomCommandGenerator.cxx
+++ b/Source/cmCustomCommandGenerator.cxx
@@ -29,7 +29,7 @@ namespace {
 std::string EvaluateSplitConfigGenex(
   cm::string_view input, cmGeneratorExpression const& ge, cmLocalGenerator* lg,
   bool useOutputConfig, std::string const& outputConfig,
-  std::string const& commandConfig,
+  std::string const& commandConfig, cmGeneratorTarget const* target,
   std::set<BT<std::pair<std::string, bool>>>* utils = nullptr)
 {
   std::string result;
@@ -87,7 +87,7 @@ std::string EvaluateSplitConfigGenex(
     // Evaluate this genex in the selected configuration.
     std::unique_ptr<cmCompiledGeneratorExpression> cge =
       ge.Parse(std::string(genex));
-    result += cge->Evaluate(lg, *config);
+    result += cge->Evaluate(lg, *config, target);
 
     // Record targets referenced by the genex.
     if (utils) {
@@ -114,7 +114,8 @@ std::vector<std::string> EvaluateDepends(std::vector<std::string> const& paths,
     std::string const& ep =
       EvaluateSplitConfigGenex(p, ge, lg, /*useOutputConfig=*/true,
                                /*outputConfig=*/outputConfig,
-                               /*commandConfig=*/commandConfig);
+                               /*commandConfig=*/commandConfig,
+                               /*target=*/nullptr);
     cm::append(depends, cmExpandedList(ep));
   }
   for (std::string& p : depends) {
@@ -157,6 +158,7 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(
   : CC(&cc)
   , OutputConfig(crossConfig ? *crossConfig : config)
   , CommandConfig(std::move(config))
+  , Target(cc.GetTarget())
   , LG(lg)
   , OldStyle(cc.GetEscapeOldStyle())
   , MakeVars(cc.GetEscapeAllowMakeVars())
@@ -171,6 +173,8 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(
   }
 
   cmGeneratorExpression ge(cc.GetBacktrace());
+  cmGeneratorTarget const* target{ lg->FindGeneratorTargetToUse(
+    this->Target) };
 
   const cmCustomCommandLines& cmdlines = this->CC->GetCommandLines();
   for (cmCustomCommandLine const& cmdline : cmdlines) {
@@ -180,7 +184,7 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(
     for (std::string const& clarg : cmdline) {
       std::string parsed_arg = EvaluateSplitConfigGenex(
         clarg, ge, this->LG, useOutputConfig, this->OutputConfig,
-        this->CommandConfig, &this->Utilities);
+        this->CommandConfig, target, &this->Utilities);
       if (this->CC->GetCommandExpandLists()) {
         cm::append(argv, cmExpandedList(parsed_arg));
       } else {
@@ -249,9 +253,9 @@ cmCustomCommandGenerator::cmCustomCommandGenerator(
 
   const std::string& workingdirectory = this->CC->GetWorkingDirectory();
   if (!workingdirectory.empty()) {
-    this->WorkingDirectory =
-      EvaluateSplitConfigGenex(workingdirectory, ge, this->LG, true,
-                               this->OutputConfig, this->CommandConfig);
+    this->WorkingDirectory = EvaluateSplitConfigGenex(
+      workingdirectory, ge, this->LG, true, this->OutputConfig,
+      this->CommandConfig, target);
     // Convert working directory to a full path.
     if (!this->WorkingDirectory.empty()) {
       std::string const& build_dir = this->LG->GetCurrentBinaryDirectory();
diff --git a/Source/cmCustomCommandGenerator.h b/Source/cmCustomCommandGenerator.h
index e70909a74a0f7190db37797794df480ee3f84eba..73a8d387deadde59b8cf5b4c3ed33f9856d88451 100644
--- a/Source/cmCustomCommandGenerator.h
+++ b/Source/cmCustomCommandGenerator.h
@@ -25,6 +25,7 @@ class cmCustomCommandGenerator
   cmCustomCommand const* CC;
   std::string OutputConfig;
   std::string CommandConfig;
+  std::string Target;
   cmLocalGenerator* LG;
   bool OldStyle;
   bool MakeVars;
diff --git a/Source/cmLocalGenerator.cxx b/Source/cmLocalGenerator.cxx
index 0c686aa2963f55d4be76d737ded8d08c3e58c68b..6a49b84a797ff9cecf852b520a83fd1945abb82b 100644
--- a/Source/cmLocalGenerator.cxx
+++ b/Source/cmLocalGenerator.cxx
@@ -4096,6 +4096,7 @@ void AddCustomCommandToTarget(cmLocalGenerator& lg,
   cc.SetDepfile(depfile);
   cc.SetJobPool(job_pool);
   cc.SetCMP0116Status(cmp0116);
+  cc.SetTarget(target->GetName());
   switch (type) {
     case cmCustomCommandType::PRE_BUILD:
       target->AddPreBuildCommand(std::move(cc));
diff --git a/Tests/RunCMake/add_custom_command/RunCMakeTest.cmake b/Tests/RunCMake/add_custom_command/RunCMakeTest.cmake
index 9c59b4b75e538b11c7625bd4618f044752d22cc6..ad6b2586c55d0d55773693861828a0c1ff28a318 100644
--- a/Tests/RunCMake/add_custom_command/RunCMakeTest.cmake
+++ b/Tests/RunCMake/add_custom_command/RunCMakeTest.cmake
@@ -43,3 +43,20 @@ if(NOT RunCMake_GENERATOR STREQUAL "Ninja Multi-Config")
   unset(RunCMake_TEST_BINARY_DIR)
   unset(RunCMake_TEST_NO_CLEAN)
 endif()
+
+function(test_genex name)
+  run_cmake(${name})
+
+  set(RunCMake_TEST_BINARY_DIR "${RunCMake_BINARY_DIR}/${name}-build")
+  set(RunCMake_TEST_NO_CLEAN 1)
+  run_cmake_command(${name}-build ${CMAKE_COMMAND} --build .)
+
+  if(NOT EXISTS "${RunCMake_TEST_BINARY_DIR}/wdir/touched")
+    message(SEND_ERROR "File not created by target-dependent add_custom_command()!")
+  endif()
+
+  unset(RunCMake_TEST_NO_CLEAN)
+  unset(RunCMake_TEST_BINARY_DIR)
+endfunction()
+
+test_genex(TargetGenexEvent)
diff --git a/Tests/RunCMake/add_custom_command/TargetGenexEvent.cmake b/Tests/RunCMake/add_custom_command/TargetGenexEvent.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..8591b74fce6b787bfd1382d56e11bfef6ee6d8d9
--- /dev/null
+++ b/Tests/RunCMake/add_custom_command/TargetGenexEvent.cmake
@@ -0,0 +1,10 @@
+add_custom_target(target ALL)
+set_target_properties(target PROPERTIES COMPILE_DEFINITIONS "touched" COMPILE_OPTIONS "${CMAKE_BINARY_DIR}/wdir")
+file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/wdir")
+
+add_custom_command(
+  TARGET target
+  POST_BUILD
+  COMMAND ${CMAKE_COMMAND} -E touch $<TARGET_PROPERTY:COMPILE_DEFINITIONS>
+  WORKING_DIRECTORY $<TARGET_PROPERTY:COMPILE_OPTIONS>
+)