Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: symlinks handling #3298

Open
wants to merge 60 commits into
base: master
Choose a base branch
from
Open

Conversation

2ZeroSix
Copy link
Contributor

@2ZeroSix 2ZeroSix commented Feb 1, 2022

fixes: #3143

@jonasfj
Copy link
Member

jonasfj commented Feb 1, 2022

I'm a bit unsure if we want to support this, how does it handle cycles?
What is the need for symlink directories?

Might it not be better to just ask advanced users to copy files if they need to include them in multiple locations. They can wrap their dart pub publish command with a bash script living in tool/ -- it's ugly, but how many people actually need this?

@2ZeroSix
Copy link
Contributor Author

2ZeroSix commented Feb 1, 2022

It’s common use case for macOS/iOS plug-ins, third party libraries in ffi plugins etc

In our case it forces us to remove directory with symlink to common scripts before publish

pubignore also does not solve it since this check is done prior to application of ignore rules

@2ZeroSix
Copy link
Contributor Author

2ZeroSix commented Feb 1, 2022

  • added test for cycles
  • fixed wrongly appended / to path consisted of single delimiter

The only failing test should be one which ensures lish is failing on package with symlink to directory.
This seems to be a regression and was introduced somewhere between flutter 2.5+ and 2.8- (dart 2.12 and 2.15)

Previous behavior (pub bundled in dart 2.12 and behavior in current PR):

  • pub archives all filtered files in tar.gz

  • consumer receives them as separate files after unpacking of tar.gz

  • so, there is no OS-dependant problems for consumer and no headache for package developer

Current behavior:

  • pub throws on any (even ignored) directory symlink beneath package directory
  • package developers should apply workarounds to their pipelines
  • consumers doesn't see any difference

If you insist this is expected behavior: I could try to move the check after resolving of ignored files and allow only ignored directory symlinks

@2ZeroSix
Copy link
Contributor Author

2ZeroSix commented Feb 3, 2022

Hmm, it seems that in windows it fails to list dirrectory with loops in symlinks

It differs from what is done on posix-like systems

02:34 +32 -1: test/package_list_files_test.dart: throws on symlink cycles
02:34 +32 -2: test/package_list_files_test.dart: throws on symlink cycles [E]
  Expected: throws <Instance of 'DataException'> with `message`: contains 'Pub does not support publishing packages with non-resolving symlink:'
    Actual: <Closure: () => List<String>>
     Which: threw FileSystemException:<FileSystemException: Directory listing failed, path = 'C:\Users\runneradmin\AppData\Local\Temp\dart_test_5e4ddcd9\myapp\subdir\symlink\subdir\symlink\subdir\symlink\subdir\symlink\subdir\symlink\subdir\symlink\subdir\symlink\subdir\symlink\subdir\symlink\subdir\symlink\subdir\symlink\subdir\symlink\subdir\symlink\*' (OS Error: The system cannot find the path specified.
                  , errno = 3)>
            stack dart:io                                   _Directory.listSync
                  package:pub/src/package.dart 248:48       Package.listFiles.<fn>
                  package:pub/src/ignore.dart 291:28        Ignore.listFiles
                  package:pub/src/package.dart 245:19       Package.listFiles
                  test\package_list_files_test.dart 115:30  main.<fn>.<fn>
                  package:test_api                          expect
                  test\package_list_files_test.dart 114:5   main.<fn>
                  
            which is not an instance of 'DataException'
  
  package:test_api                         expect
  test\package_list_files_test.dart 114:5  main.<fn>

lib/src/package.dart Outdated Show resolved Hide resolved
lib/src/package.dart Outdated Show resolved Hide resolved
@sigurdm
Copy link
Contributor

sigurdm commented Feb 10, 2022

Besides the cycle-handling this is looking quite good!

@2ZeroSix
Copy link
Contributor Author

jfyi: I had no time to reslove windows-related bugs lately, will continue in a few days

@sigurdm
Copy link
Contributor

sigurdm commented Oct 22, 2024

I have done a bit of research, and it seems that both on windows, mac and linux resolveSymbolicLinksSync will fail with an exception if the nesting of a symlink to resolve is too deep.

By that logic we probably don't need to do anything explicit to prevent circularities. We just handle that exception.

I'll try to make a merge, and an update without the check for circularities.

@sigurdm
Copy link
Contributor

sigurdm commented Oct 22, 2024

Hmm - seems the CI doesn't agree quite with my research.

On mac it seems contents of sufficiently nested directories is not listed. And on windows sufficiently nested links exists as Link but not as a Directory...

@spydon
Copy link

spydon commented Oct 22, 2024

Hmm - seems the CI doesn't agree quite with my research.

On mac it seems contents of sufficiently nested directories is not listed. And on windows sufficiently nested links exists as Link but not as a Directory...

Does that matter if it is documented? It would still work for all use cases where everything is set up correctly right?

@sigurdm
Copy link
Contributor

sigurdm commented Oct 24, 2024

Yeah - perhaps we don't need to care that much about symlink cycles. If you have them, you kind of set yourself up for trouble...

@sigurdm
Copy link
Contributor

sigurdm commented Oct 29, 2024

@jonasfj I reinstated some symlink detection. I think™ this works.

  • For every visited directory x, it resolves canonicalize(x) to x'.
  • Then for every parent of canonicalize(x), p:
    • it resolves p to p'. If x' equals p' it reports a cycle.

Could you do a sanity check?

Copy link
Member

@jonasfj jonasfj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will work.

Comment on lines 289 to 293
} on FileSystemException catch (e) {
throw DataException(
'Could not resolve symbolic link $path. $e',
);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
} on FileSystemException catch (e) {
throw DataException(
'Could not resolve symbolic link $path. $e',
);
}
} on FileSystemException catch (e) {
if (!link.existsSync()) {
return;
}
throw DataException(
'Could not resolve symbolic link $path. $e',
);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

test/package_list_files_test.dart Outdated Show resolved Hide resolved
Comment on lines 139 to 142
createDirectorySymlink(
p.join(d.sandbox, appPath, 'subdir', 'symlink'),
'..',
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making a d.link method

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

test/package_list_files_test.dart Show resolved Hide resolved
@sigurdm sigurdm requested a review from jonasfj October 31, 2024 13:21
Comment on lines 20 to 31
await dir(appPath, [
dir('b', [file('bb', 'bbb')]),
]).create();
Link(p.join(sandbox, appPath, 'symlink_to_dir_outside_package'))
.createSync(p.join(sandbox, 'a'));
Link(p.join(sandbox, appPath, 'symlink_to_dir_outside_package_relative'))
.createSync(p.join('..', 'a'));
Link(p.join(sandbox, appPath, 'symlink_to_dir_inside_package'))
.createSync(p.join(sandbox, appPath, 'b'));
Link(p.join(sandbox, appPath, 'symlink_to_dir_inside_package_relative'))
.createSync('b');
Link(p.join(sandbox, appPath, 'b', 'l')).createSync(p.join(sandbox, 't'));
Copy link
Member

@jonasfj jonasfj Oct 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use link?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Belongs in the test/descriptor/ folder:
https://github.com/dart-lang/pub/tree/master/test/descriptor

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 54 to 56
d.Descriptor link(String name, String target, {bool forceDirectory = false}) {
return LinkDescriptor(name, target, forceDirectory: forceDirectory);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this in test/descriptor.dart, I think..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import 'package:tar/tar.dart';
import 'package:test/test.dart';

import '../descriptor.dart';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import '../descriptor.dart';
import '../descriptor.dart' as d;

To follow the pattern we have in other files.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Pub does not support publishing packages with directory symlinks
6 participants