<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Håkon Hægland</title>
    <description>The latest articles on DEV Community by Håkon Hægland (@hakonhagland).</description>
    <link>https://dev.to/hakonhagland</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F606812%2F8c41f53b-0be0-40c3-a4c2-165791562a8a.jpeg</url>
      <title>DEV Community: Håkon Hægland</title>
      <link>https://dev.to/hakonhagland</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hakonhagland"/>
    <language>en</language>
    <item>
      <title>Using Google Drive with Perl</title>
      <dc:creator>Håkon Hægland</dc:creator>
      <pubDate>Thu, 14 Oct 2021 22:23:47 +0000</pubDate>
      <link>https://dev.to/hakonhagland/using-google-drive-with-perl-5gli</link>
      <guid>https://dev.to/hakonhagland/using-google-drive-with-perl-5gli</guid>
      <description>&lt;p&gt;I often use &lt;a href="https://en.wikipedia.org/wiki/Google_Drive" rel="noopener noreferrer"&gt;Google Drive&lt;/a&gt; to transfer files between devices, for example from my Mac to my Linux laptop. It works great, but there can be some boring repetitive work involved depending on how often you do the same operation. For example, when I want to transfer a file from the Mac, I open up a web browser (usually Google Chrome) and point it to &lt;code&gt;drive.google.com&lt;/code&gt;, the Google Drive web interface. From here, I try to locate the directory I want to upload the file to which might involve some scrolling in the directory listing. After that, I need to click on the directory name to view its content. Next, I right-click on an empty area in the directory list and select "Upload files" from a drop-down menu. Then a file selection dialog appears and I have to locate the directory and file I want to upload in the dialog, and after some more mouse movements and clicks I finally press the "Ok" button to select the file. Similarly, on the other device (like my Linux laptop) where I want to download the file, I need to locate the file in web UI and then right-click it and select "Download" from the drop-down menu to download the file. &lt;/p&gt;

&lt;p&gt;Luckily, Google has provided the Google Drive API that can be used by user scripts to communicate with the cloud server. So the first thing I wanted to check was if someone already had written Perl code that made use of this API. After a quick search on &lt;a href="https://en.wikipedia.org/wiki/CPAN" rel="noopener noreferrer"&gt;MetaCPAN&lt;/a&gt; I found a module  &lt;a href="https://metacpan.org/pod/Net::Google::Drive::Simple" rel="noopener noreferrer"&gt;Net::Google::Drive::Simple&lt;/a&gt; that looked promising. When reading through the module's documentation, I realized that the tricky part could be to get hold of an "access token". The script needs this token to be able to connect to the Google Drive API server. The module claimed that it could generate the access token from a secret credential token which I first had to obtain from Google Cloud. Then, when the module had generated this access token the rest should be fairly simple was my impression. Well.., let's see what happend 👾&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Google account
&lt;/h2&gt;

&lt;p&gt;In order to get the Google Drive credentials, the first thing you need to have is a Google account (obviously). This allows you to use Google developer products, including Google Cloud Console, Cloud SDK, Cloud Logging, and Cloud Monitoring. If you don't have a Google account, you can visit   &lt;a href="https://cloud.google.com/apis/docs/getting-started" rel="noopener noreferrer"&gt;https://cloud.google.com/apis/docs/getting-started&lt;/a&gt; to get started. Creating a Google account, with some amount of cloud storage is free, see &lt;a href="https://cloud.google.com/free" rel="noopener noreferrer"&gt;https://cloud.google.com/free&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Google project
&lt;/h2&gt;

&lt;p&gt;Next, to use the Cloud APIs you need to set up a Google project. A project is equivalent to a developer account. It serves as a resource container for your Google Cloud resources and provides an isolation boundary for your usage of Google Cloud services, so you can manage quota limits and billing independently at the project level. Usage telemetry and dashboards are grouped by projects as well. If you don't already have a project, you can create one using the Cloud Console, see &lt;a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects" rel="noopener noreferrer"&gt;this&lt;/a&gt; link for more information. Here is what I did to create a new project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the &lt;a href="https://console.cloud.google.com/apis/dashboard" rel="noopener noreferrer"&gt;Google Cloud console&lt;/a&gt;, click the drop down menu in top left corner, and select "IAM &amp;amp; Admin" -&amp;gt; "Manage Resources",&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;from the "Select organization" drop-down list at the top of the page, you can select the organization in which you want to create a project. If you are a free trial user, skip this step, as this list does not appear.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then click "Create Project", and in the "New Project" window that appears, enter a project name.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlsm3j83guxata4j0tgi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlsm3j83guxata4j0tgi.png" alt="Alt Text"&gt;&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;A project name can contain only letters, numbers, single quotes, hyphens, spaces, or exclamation points, and must be between 4 and 30 characters. I chose "DesktopApp" for the project name, and will use this name in the further discussion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter the parent organization or folder in the "Location" box. That resource will be the hierarchical parent of the new project. If you do not have a parent folder, just leave "No organization" (as I did) in the "Location" box.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you're finished entering new project details, click the "CREATE" button.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Enabling the Google Drive API
&lt;/h2&gt;

&lt;p&gt;Next, in order to use Google Drive API from a script you need to enable it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the &lt;a href="https://console.cloud.google.com/apis/dashboard" rel="noopener noreferrer"&gt;Google Cloud console&lt;/a&gt;, click the drop down menu in top left corner, and select "APIs &amp;amp; Services" -&amp;gt; "Library". Type "Google Drive" in the search bar. Select "Google Drive API" from the result list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the "DesktopApp" project, by clicking project button in the top menu bar. If it already shows "DesktopApp", you can skip this step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the "ENABLE" button.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configure the app's scopes and the consent screen
&lt;/h2&gt;

&lt;p&gt;The script that we will write to access Google Drive is called an "app". The "user" of the script is in our case the same as the writer (ourselves) of the script, but it need not be. You could send the script to someone else or publish it on the internet. In order for the app to get access to the user's Google Drive, the user must approve that script can access his drive under a certain "scope". For example, the user may give the script read-only access. &lt;/p&gt;

&lt;p&gt;The first time the script is trying to access the drive, the user is presented with a "consent screen" where they can select which scopes the script can use to access their drive. After the user has given their consent, the Google Cloud server returns an access token to the script which it can use to access the drive without asking the user for permission the next time. The access token will expire after a certain time, and then the script needs to refresh the token. The user can also revoke the access right of the script at a later time if they so wishes, then the script's access token becomes invalid and it cannot be refreshed. The script must then repeat the process with presenting the user with the consent screen in order to get further access. Read more about the OAuth2 protocol for authentication &lt;a href="https://developers.google.com/identity/protocols/oauth2" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, in order to setup what scopes should be presented to the user in the consent screen (see &lt;a href="https://developers.google.com/workspace/guides/create-credentials" rel="noopener noreferrer"&gt;workspace/guides/create-credentials&lt;/a&gt;) do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the &lt;a href="https://console.cloud.google.com/apis/dashboard" rel="noopener noreferrer"&gt;Google Cloud console&lt;/a&gt;, click the drop down menu in top left corner, and select "APIs &amp;amp; Services" -&amp;gt; "Credentials". The credential page for your project appears.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the "DesktopApp" project, by clicking project button in the top menu bar. If it already shows "DesktopApp", you can skip this step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the "CONFIGURE CONSENT SCREEN" button on the right side. (If you have already configured the consent screen and want to modify it, you should instead click on "OAuth consent screen" in the left side bar)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj19n9m1x6124aeamcq1r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj19n9m1x6124aeamcq1r.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Select the user type for the app. Click "External".&lt;br&gt;
(If you are a google workspace user and wants the app to be internal to your organization, you could select "Internal" instead.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the "CREATE" button&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the App information screen, type in a name for the app. This name will appear in the consent screen that is presented to the user during the authorization procedure.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpb1cge1fff0tuzltist.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpb1cge1fff0tuzltist.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Enter your email in the "User support email" field.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Skip the "App logo" field.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Also skip the "App domain" fields.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Also skip the "Authorized domains" field.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enter your email address in the "Developer contact information" field.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the "SAVE AND CONTINUE" button&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the "Edit app registration" screen, click the "ADD OR REMOVE SCOPES" button.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbf84frm5bwu67oh8j5f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhbf84frm5bwu67oh8j5f.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;From the popup window "Update selected scopes" click the checkmark next to the following scopes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;code&gt;.../auth/drive.file&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;.../auth/drive.metadata.readonly&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;.../auth/drive.readonly&lt;/code&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wklz7q6ll7fxou15ne3.png" alt="Alt Text"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that only the scopes you select in the popup window can be requested by the app. For instance, if the app later requests all of the above three scopes, the user will be presented with a consent screen with the three alternatives, like this: &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foigchzayg9mjwck70h6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foigchzayg9mjwck70h6g.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;br&gt;
  The first alternative in the screen shot above corresponds to the &lt;code&gt;drive.readonly&lt;/code&gt; scope, the second corresponds to the &lt;code&gt;drive.metadata.readonly&lt;/code&gt; scope, and the third one corresponds to the &lt;code&gt;drive.file&lt;/code&gt; scope.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the "UPDATE" button at the bottom of the window&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click "SAVE AND CONTINUE" in the "Edit app registration" screen.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In the "Test users" screen, click the "+ ADD USERS" button, and add yourself (your gmail address) to the list.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9asem9f77t2f1p7ikrg7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9asem9f77t2f1p7ikrg7.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click "SAVE AND CONTINUE".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Summary screen is shown. Click the "BACK TO DASHBOARD" button.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Create credentials
&lt;/h2&gt;

&lt;p&gt;In order for the app (the script) to obtain an access token, it needs to pass a set of credentials to to the Google authorization server. Together with the credentials, the script passes (as a parameter) the set of scopes that it would like to access. The authorization server then present the user of the script with a screen where they can log in with their Google account. After logging in, the user is asked whether they are willing to grant one or more permissions that your app is requesting. This process is called user consent as discussed in the previous section.&lt;/p&gt;

&lt;p&gt;To obtain the set of credentials that the script should present to the authorization server (see: &lt;a href="https://developers.google.com/workspace/guides/create-credentials" rel="noopener noreferrer"&gt;workspace/guides/create-credentials&lt;/a&gt;), we do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In the Google cloud console, click the drop down menu in top left corner, and select "APIs &amp;amp; Services" -&amp;gt; "Credentials". The credential page for your project appears.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9iq785aga4qfahi6sirs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9iq785aga4qfahi6sirs.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the "DesktopApp" project, by clicking project button in the top menu bar. If it already shows "DesktopApp", you can skip this step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the "+ CREATE CREDENTIALS" button on the top of the&lt;br&gt;
page. Select "OAuth Client ID" from the drop down menu.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select "Desktop app" from the "Application type" drop down menu.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select a client name in the "Name" field, or use the suggested name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the "CREATE" button at the bottom of the page.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click "OK" in the "OAuth client created" dialog box.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next, still in the Google cloud console: click the drop down menu in top left corner, and select "APIs &amp;amp; Services" -&amp;gt; "Credentials". The credential page for your project appears.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the "DesktopApp" project, by clicking project button in the top menu bar. If it already shows "DesktopApp", you can skip this step.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under "OAuth 2.0 Client IDs", click the download button under the "Actions" heading.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4b8mwjqq5q4gpqc3gii5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4b8mwjqq5q4gpqc3gii5.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select "DOWNLOAD JSON" in the popup window.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rename the file as "credentials.json" on your computer.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Obtaining the access token
&lt;/h2&gt;

&lt;p&gt;Now as we have the credentials, we can obtain the access token. I have rewritten the &lt;a href="https://metacpan.org/release/ATOOMIC/Net-Google-Drive-Simple-0.19/source/eg/google-drive-init" rel="noopener noreferrer"&gt;google-drive-init&lt;/a&gt; script provided by &lt;a href="https://metacpan.org/pod/Net::Google::Drive::Simple" rel="noopener noreferrer"&gt;Net::Google::Drive::Simple&lt;/a&gt; slightly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;v5&lt;/span&gt;&lt;span class="mf"&gt;.26&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# Indented here-docs introduced in 5.26&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;feature&lt;/span&gt; &lt;span class="sx"&gt;qw(say)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;experimental&lt;/span&gt; &lt;span class="sx"&gt;qw(signatures)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt; &lt;span class="sx"&gt;qw(:std :encoding(utf-8))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;OAuth::Cmdline::&lt;/span&gt;&lt;span class="nv"&gt;GoogleDrive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;OAuth::Cmdline::&lt;/span&gt;&lt;span class="nv"&gt;Mojo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;read_credentials&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$token_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
    &lt;span class="c1"&gt;# NOTE: if you change the scopes below, you should delete the previous&lt;/span&gt;
    &lt;span class="c1"&gt;#  access token file (.google-drive.yml) and then rerun this script..&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@scopes&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drive.file&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
        &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drive.metadata.readonly&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
        &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;drive.readonly&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$scopes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;join&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.googleapis.com/auth/&lt;/span&gt;&lt;span class="si"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;@scopes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$oauth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OAuth::Cmdline::&lt;/span&gt;&lt;span class="nv"&gt;GoogleDrive&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;client_id&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;client_secret&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;login_uri&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;auth_uri&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;token_uri&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$credentials&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;token_uri&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;scope&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$scopes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;homedir&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$token_dir&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;INFO_STR&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
    &lt;span class="nv"&gt;Running&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt; &lt;span class="nv"&gt;collector&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="nv"&gt;server&lt;/span&gt; &lt;span class="nv"&gt;at&lt;/span&gt; &lt;span class="nv"&gt;http:&lt;/span&gt;&lt;span class="sr"&gt;//local&lt;/span&gt;&lt;span class="nv"&gt;host:8082&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;Open&lt;/span&gt; &lt;span class="nv"&gt;your&lt;/span&gt; &lt;span class="nv"&gt;web&lt;/span&gt; &lt;span class="nv"&gt;browser&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;go&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;http:&lt;/span&gt;&lt;span class="sr"&gt;//local&lt;/span&gt;&lt;span class="nv"&gt;host:8082&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="nv"&gt;click&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nb"&gt;link&lt;/span&gt;
      &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login on google-drive&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;fill&lt;/span&gt; &lt;span class="nv"&gt;out&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;OAuth&lt;/span&gt; &lt;span class="nv"&gt;consent&lt;/span&gt; &lt;span class="nv"&gt;screen&lt;/span&gt; &lt;span class="nv"&gt;granting&lt;/span&gt;
      &lt;span class="nv"&gt;your&lt;/span&gt; &lt;span class="nv"&gt;Perl&lt;/span&gt; &lt;span class="nv"&gt;scripts&lt;/span&gt; &lt;span class="nv"&gt;access&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;your&lt;/span&gt; &lt;span class="nv"&gt;google&lt;/span&gt; &lt;span class="nv"&gt;drive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;Click&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;check&lt;/span&gt; &lt;span class="nv"&gt;boxes&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;what&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt; &lt;span class="nv"&gt;can&lt;/span&gt; &lt;span class="nv"&gt;access&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;Then&lt;/span&gt; &lt;span class="nv"&gt;click&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Continue&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;When&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;message&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Tokens saved in ./.google-drive.yml&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;appears&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt;
      &lt;span class="nv"&gt;access&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt; &lt;span class="nv"&gt;was&lt;/span&gt; &lt;span class="nv"&gt;successfully&lt;/span&gt; &lt;span class="nv"&gt;generated&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;saved&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;file&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;google&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;drive&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;yml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;You&lt;/span&gt; &lt;span class="nv"&gt;can&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="nv"&gt;quit&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;server&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt; &lt;span class="nv"&gt;script&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;by&lt;/span&gt; &lt;span class="nv"&gt;pressing&lt;/span&gt; &lt;span class="nv"&gt;CTRL&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="o"&gt;----------------------------------------------------------------&lt;/span&gt;
    &lt;span class="nv"&gt;INFO_STR&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OAuth::Cmdline::&lt;/span&gt;&lt;span class="nv"&gt;Mojo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;oauth&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$oauth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daemon&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-l&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="nv"&gt;$oauth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;local_uri&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;# After you filled out the consent screens, you need to press CTRL-C in the shell&lt;/span&gt;
    &lt;span class="c1"&gt;#  window to exit this script.&lt;/span&gt;
    &lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Server finished.&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
    &lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Done.&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="nf"&gt;read_credentials&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;credentials.json&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
    &lt;span class="nb"&gt;open&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$fh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="nv"&gt;$fn&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;die&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Could not open file '&lt;/span&gt;&lt;span class="si"&gt;$fn&lt;/span&gt;&lt;span class="s2"&gt;': $!&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="vg"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;$fh&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nb"&gt;close&lt;/span&gt; &lt;span class="nv"&gt;$fh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$json&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$str&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;die&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$fn&lt;/span&gt;&lt;span class="s2"&gt;: Expected installed app&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;exists&lt;/span&gt; &lt;span class="nv"&gt;$hash&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;installed&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$hash&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;installed&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run this script (after installing &lt;a href="https://metacpan.org/pod/OAuth::Cmdline" rel="noopener noreferrer"&gt;OAuth::Cmdline&lt;/a&gt;), the access token should be saved in a file &lt;code&gt;.google-drive.yml&lt;/code&gt; in the current directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading a file to Google Drive
&lt;/h2&gt;

&lt;p&gt;Finally, we have the access token and we can start to use the module &lt;a href="https://metacpan.org/pod/Net::Google::Drive::Simple" rel="noopener noreferrer"&gt;Net::Google::Drive::Simple&lt;/a&gt;. We will start by uploading a file to Google Drive. Unfortunately, when I tested the &lt;a href="https://metacpan.org/pod/Net::Google::Drive::Simple#METHODS" rel="noopener noreferrer"&gt;&lt;code&gt;file_upload( $file, $dir_id )&lt;/code&gt;&lt;/a&gt; method, it did not work as expected, see &lt;a href="https://github.com/mschilli/net-google-drive-simple/issues/47" rel="noopener noreferrer"&gt;issue # 47&lt;/a&gt;. I created &lt;a href="https://github.com/mschilli/net-google-drive-simple/pull/48" rel="noopener noreferrer"&gt;a pull request&lt;/a&gt; to fix the issue. Since the pull request has not yet been merged I suggest that you (if you want to upload files) install the module (&lt;code&gt;Net::Google::Drive::Simple&lt;/code&gt;) like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mschilli/net-google-drive-simple.git
&lt;span class="nb"&gt;cd &lt;/span&gt;net-google-drive-simple
git fetch origin pull/48/head:pr_48
git checkout pr_48
cpanm &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, create a dummy &lt;code&gt;hello.txt&lt;/code&gt; file that we will upload to the root directory of Google Drive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello world!"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; hello.txt 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then save the following script as &lt;code&gt;upload.pl&lt;/code&gt; (in the same directory as you have the access token file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;feature&lt;/span&gt; &lt;span class="sx"&gt;qw(say)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Net::Google::Drive::&lt;/span&gt;&lt;span class="nv"&gt;Simple&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;OAuth::Cmdline::&lt;/span&gt;&lt;span class="nv"&gt;GoogleDrive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$token_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$oauth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OAuth::Cmdline::&lt;/span&gt;&lt;span class="nv"&gt;GoogleDrive&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;scope&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.googleapis.com/auth/drive.file&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
    &lt;span class="s"&gt;homedir&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$token_dir&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$gd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Net::Google::Drive::&lt;/span&gt;&lt;span class="nv"&gt;Simple&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;oauth&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$oauth&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello.txt&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$gd&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;file_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$parent&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;die&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Upload failed: $!&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Uploaded file: &lt;/span&gt;&lt;span class="si"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt; (id: &lt;/span&gt;&lt;span class="si"&gt;$file_id&lt;/span&gt;&lt;span class="s2"&gt;) to directory with id: &lt;/span&gt;&lt;span class="si"&gt;$parent&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;perl upload.pl
Uploaded file: hello.txt &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;: 1x5pc9_rvAFeLgkp0zLUeuKAIMWptplz9&lt;span class="o"&gt;)&lt;/span&gt; to directory with &lt;span class="nb"&gt;id&lt;/span&gt;: root
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to upload the &lt;code&gt;hello.txt&lt;/code&gt; file to your Google Drive!&lt;/p&gt;

&lt;h2&gt;
  
  
  Downloading a file from Google Drive
&lt;/h2&gt;

&lt;p&gt;It is not enough to simply specify the directory and filename to download a file. There can be multiple files with the same name in the same directory, however a file's ID is unique. When we uploaded the &lt;code&gt;hello.txt&lt;/code&gt; the API returned the ID of the uploaded file, namely &lt;code&gt;1x5pc9_rvAFeLgkp0zLUeuKAIMWptplz9&lt;/code&gt;. Another way to determine the ID is to right click a file in the Google Drive UI and select "Get link" from the drop down menu. If you know that the file name is unique, a third way is to use the &lt;a href="https://metacpan.org/pod/Net::Google::Drive::Simple#METHODS" rel="noopener noreferrer"&gt;&lt;code&gt;search()&lt;/code&gt;&lt;/a&gt; method. I first show how to download a file when you know the ID. The following script can download a file that the app created (if you want to download any file, you should replace the &lt;code&gt;drive.file&lt;/code&gt; scope with the &lt;code&gt;drive.readonly&lt;/code&gt; scope) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;feature&lt;/span&gt; &lt;span class="sx"&gt;qw(say)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Net::Google::Drive::&lt;/span&gt;&lt;span class="nv"&gt;Simple&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;OAuth::Cmdline::&lt;/span&gt;&lt;span class="nv"&gt;GoogleDrive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$token_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$oauth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OAuth::Cmdline::&lt;/span&gt;&lt;span class="nv"&gt;GoogleDrive&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;scope&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.googleapis.com/auth/drive.file&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
    &lt;span class="s"&gt;homedir&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$token_dir&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$gd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Net::Google::Drive::&lt;/span&gt;&lt;span class="nv"&gt;Simple&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;oauth&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$oauth&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1x5pc9_rvAFeLgkp0zLUeuKAIMWptplz9&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;download_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Downloading from: &lt;/span&gt;&lt;span class="si"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$save_fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello2.txt&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$gd&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$save_fn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Downloaded file with id '&lt;/span&gt;&lt;span class="si"&gt;$file_id&lt;/span&gt;&lt;span class="s2"&gt;' as '&lt;/span&gt;&lt;span class="si"&gt;$save_fn&lt;/span&gt;&lt;span class="s2"&gt;'&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;die&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to download &lt;/span&gt;&lt;span class="si"&gt;$file_id&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="nv"&gt;$gd&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="nf"&gt;download_url&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/drive/v2/files/&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="vg"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;?alt=media&amp;amp;source=downloadUrl&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, if you know that the file name is unique across the whole Google Drive and you don't want to look in the UI for the file ID, you can use the &lt;code&gt;search()&lt;/code&gt; method like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;feature&lt;/span&gt; &lt;span class="sx"&gt;qw(say)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Net::Google::Drive::&lt;/span&gt;&lt;span class="nv"&gt;Simple&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;OAuth::Cmdline::&lt;/span&gt;&lt;span class="nv"&gt;GoogleDrive&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$token_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$oauth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OAuth::Cmdline::&lt;/span&gt;&lt;span class="nv"&gt;GoogleDrive&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;scope&lt;/span&gt;         &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;  &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.googleapis.com/auth/drive.readonly&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt;
                     &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.googleapis.com/auth/drive.file&lt;/span&gt;&lt;span class="p"&gt;"),&lt;/span&gt;
    &lt;span class="s"&gt;homedir&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$token_dir&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$gd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Net::Google::Drive::&lt;/span&gt;&lt;span class="nv"&gt;Simple&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;oauth&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$oauth&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello.txt&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$children&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$gd&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;maxResults&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title = '&lt;/span&gt;&lt;span class="si"&gt;$fn&lt;/span&gt;&lt;span class="s2"&gt;'&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;die&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Multiple files matching '&lt;/span&gt;&lt;span class="si"&gt;$fn&lt;/span&gt;&lt;span class="s2"&gt;' found.&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;@$children&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;die&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No files matching '&lt;/span&gt;&lt;span class="si"&gt;$fn&lt;/span&gt;&lt;span class="s2"&gt;' found.&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;@$children&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$children&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$save_fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hello2.txt&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$gd&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;download&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$save_fn&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Downloaded file with name '&lt;/span&gt;&lt;span class="si"&gt;$fn&lt;/span&gt;&lt;span class="s2"&gt;' as '&lt;/span&gt;&lt;span class="si"&gt;$save_fn&lt;/span&gt;&lt;span class="s2"&gt;'&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;die&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to download &lt;/span&gt;&lt;span class="si"&gt;$fn&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="nv"&gt;$gd&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>perl</category>
      <category>googledrive</category>
      <category>googlecloud</category>
    </item>
    <item>
      <title>Handling of symlinks on Windows (Perl, MSYS2, Cygwin)</title>
      <dc:creator>Håkon Hægland</dc:creator>
      <pubDate>Sun, 04 Jul 2021 16:14:53 +0000</pubDate>
      <link>https://dev.to/hakonhagland/handling-of-symlinks-on-windows-perl-msys2-cygwin-52h3</link>
      <guid>https://dev.to/hakonhagland/handling-of-symlinks-on-windows-perl-msys2-cygwin-52h3</guid>
      <description>&lt;p&gt;After &lt;a href="https://dev.to/perldean/vscode-as-a-perl-ide-3cco"&gt;reading about&lt;/a&gt; the Perl language server module for VS Code,&lt;br&gt;
I was eager to test it since I had been missing a possibility to debug Perl code from inside VS Code. In the past I have been using emacs as my primary editor for a long time, but am gradually using VS Code more and more now. VS Code has built-in debugging support for the Node.js runtime and can debug JavaScript, TypeScript. For debugging other languages one can install Debugger extensions in the VS Code Marketplace. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://marketplace.visualstudio.com/items?itemName=richterger.perl" rel="noopener noreferrer"&gt;Language Server and Debugger for Perl&lt;/a&gt; VS Code extension allows debugging of Perl scripts, see the link above for an extensive list of features. To use the extension you first need to install the Perl module &lt;a href="https://metacpan.org/pod/Perl::LanguageServer" rel="noopener noreferrer"&gt;Perl::LanguageServer&lt;/a&gt;. I first tried to install it on Ubuntu 21.04. The module installed fine here, but after installation the extension still did not work. I found that the reason was that I was using a custom &lt;code&gt;perl&lt;/code&gt; installed with &lt;a href="https://perlbrew.pl/" rel="noopener noreferrer"&gt;perlbrew&lt;/a&gt;. Fortunately, it was easy to fix by going into the extension settings and setting an absolute path to the binary:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotebjaxjdv9xxs8gm8go.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotebjaxjdv9xxs8gm8go.png" alt="VS Code extension settings page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, I was curious to see if it also would install on Windows. Even if my preferred platform is Linux, I occasionally use Windows. For example when trying to answer questions on Stack Overflow that are related to Perl and Windows. I am using Windows 10 (run from Ubuntu through &lt;a href="https://linuxize.com/post/how-to-install-kvm-on-ubuntu-18-04" rel="noopener noreferrer"&gt;KVM&lt;/a&gt;), Home edition, 21H1, and Strawberry perl version 5.32.1. As anticipated, I quickly ran into issues with installing dependent modules, in particular &lt;a href="https://metacpan.org/pod/IO::AIO" rel="noopener noreferrer"&gt;IO::AIO&lt;/a&gt; was difficult.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgis6g1fd88xyhzn0wakv.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgis6g1fd88xyhzn0wakv.PNG" alt="cpan Perl::LanguageServer log"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I downloaded &lt;a href="https://metacpan.org/pod/IO::AIO" rel="noopener noreferrer"&gt;the source&lt;/a&gt; and tried to install &lt;code&gt;IO::AIO&lt;/code&gt; manually (not using &lt;code&gt;cpanm&lt;/code&gt;) first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;perl Makefile.PL
[...]
*** It seems you are running perl version 5.032001, likely the "official" or
*** "standard" version. While there is nothing wrong with doing that,
*** standard perl versions 5.022 and up are not supported by IO::AIO.
*** While this might be fatal, it might also be all right - if you run into
*** problems, you might want to downgrade your perl or switch to the
*** stability branch.
***
*** If everything works fine, you can ignore this message.
***
***
*** Stability canary mini-FAQ:
***
*** Do I need to do anything?
***    With luck, no. While some distributions are known to fail
***    already, most should probably work. This message is here
***    to alert you that your perl is not supported by IO::AIO,
***    and if things go wrong, you either need to downgrade, or
***    sidegrade to the stability variant of your perl version,
***    or simply live with the consequences.
***
[...]

*** Your platform is not standards compliant. To get this module working, you need to
*** download and install win32 pthread (http://sourceware.org/pthreads-win32/).
***

Generating a gmake-style Makefile
Writing Makefile for IO::AIO
Writing MYMETA.yml and MYMETA.json

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apparently it wants me to install &lt;a href="https://sourceforge.net/projects/pthreads4w/" rel="noopener noreferrer"&gt;POSIX Threads for Windows&lt;/a&gt;. I also noticed that the &lt;a href="https://metacpan.org/release/MLEHMANN/IO-AIO-4.75/source/Makefile.PL#L67" rel="noopener noreferrer"&gt;Makefile.PL&lt;/a&gt; is calling GNU Autotools &lt;a href="https://en.wikipedia.org/wiki/Configure_script" rel="noopener noreferrer"&gt;configure script&lt;/a&gt; which, as far as I know, cannot be run without a POSIX subsystem like Cygwin or MSYS2. &lt;/p&gt;

&lt;p&gt;Since I already had Cygwin and MSYS2 installed, I decided to give &lt;a href="https://www.msys2.org/" rel="noopener noreferrer"&gt;MSYS2&lt;/a&gt; a try. According to &lt;a href="https://en.wikipedia.org/wiki/Mingw-w64" rel="noopener noreferrer"&gt;wikipedia&lt;/a&gt;, MSYS2 ("minimal system 2") is a software distribution and a development platform for Windows, based on Mingw-w64 and Cygwin, that helps to deploy code from the Unix world on Windows. Instead of providing a full environment like Cygwin does, MSYS2 focuses on being a development and deployment platform.&lt;/p&gt;

&lt;p&gt;From the MSYS2 terminal window, also using &lt;code&gt;perl&lt;/code&gt; version 5.32.1 (but still a different binary than the &lt;code&gt;perl&lt;/code&gt; used by the CMD prompt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ perl Makefile.PL
[...]
*** The stability canary says: (nothing, it was driven away by harsh weather)
***
*** It seems you are running perl version 5.032001, likely the "official" or
*** "standard" version. While there is nothing wrong with doing that,
*** standard perl versions 5.022 and up are not supported by IO::AIO.
*** While this might be fatal, it might also be all right - if you run into
*** problems, you might want to downgrade your perl or switch to the
*** stability branch.
***
*** If everything works fine, you can ignore this message.
***
[...]
Continue anyways?  [y]
configure: loading site script /etc/config.site
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.exe
checking for suffix of executables... .exe
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking how to run the C preprocessor... gcc -E
[...]
checking for st_birthtimespec... yes
checking for st_gen... no
checking for statx... no
checking for accept4... yes
configure: creating ./config.status
config.status: creating config.h
Generating a Unix-style Makefile
Writing Makefile for IO::AIO
Writing MYMETA.yml and MYMETA.json
$ make
$ make test
[...]
t/03_errors.t ... 1/12 # Failed test 9 in t/03_errors.t at line 57
#  t/03_errors.t line 57 is:       ok (!$_[0]);
# Failed test 10 in t/03_errors.t at line 58
#  t/03_errors.t line 58 is:       ok ("\\test\\" eq readlink $some_link);
t/03_errors.t ... Failed 2/12 subtests
[...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And more failed tests were to come. Most fails were due to an unexpected behavior of symlinks on Windows and MSYS2. Even if I was able to fix the issue with &lt;code&gt;IO::AIO&lt;/code&gt;, there were other issues with other modules also needed by &lt;code&gt;Perl::LanguageServer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Despite this setback, I saw it as an opportunity to learn more about symlinks on Windows. Apparently there were similar issues with other modules that also needed to be fixed. As I was working with a pull request to &lt;a href="https://github.com/jkeenan/file-copy-recursive-reduced" rel="noopener noreferrer"&gt;file-copy-recursive-reduced&lt;/a&gt; I was encouraged to write this blog post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Symlinks on Windows (MSYS2 and Cygwin)
&lt;/h3&gt;

&lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Symbolic_link" rel="noopener noreferrer"&gt;symlink&lt;/a&gt; is a file that contains a reference to another file or directory in the form of an absolute or relative path. The referenced text string is automatically followed by the operating system as a path to another file or directory. This other file or directory is called the "target". So the symlink is a second file that exists independently of its target. If a symlink is deleted, its target remains unaffected. If a symlink points to a target, and sometime later that target is moved, renamed or deleted, the symlink is not automatically updated or deleted, but continues to exist and still points to the now non-existing target. Symlinks pointing to non-existing targets are called broken symlinks.&lt;/p&gt;

&lt;p&gt;On Unix-like operating systems, the target does not have to exist when a symlink is created, so broken symlinks can even be created initially. The ability to create broken symlinks is particularly useful when copying directories. Usually, the algorithm used to copy a directory just copies the files in the order returned by the &lt;code&gt;readdir()&lt;/code&gt; system call and since it is legal to create broken symlinks it does not need to copy the target of a relative symlink before the symlink itself is copied. &lt;/p&gt;

&lt;p&gt;So on Unix-like operating systems symlinks and broken symlinks can always be created, whereas on Windows this is not always the case. Early versions of Windows did not have symlinks of any kind, Windows 95 introduced &lt;a href="https://en.wikipedia.org/wiki/Shortcut_(computing)" rel="noopener noreferrer"&gt;file shortcuts&lt;/a&gt;, Windows XP introduced native symlinks (only enabled by default for kernel mode programs), and &lt;a href="https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/" rel="noopener noreferrer"&gt;starting with&lt;/a&gt; Windows 10 Insider build 14972, native symlinks could be created without needing to elevate the console as administrator (to enable this, go to the Windows settings app and choose &lt;em&gt;"Update &amp;amp; Security"&lt;/em&gt; -&amp;gt; &lt;em&gt;"For developers"&lt;/em&gt;, and turn on &lt;em&gt;"Developer mode"&lt;/em&gt;). &lt;/p&gt;

&lt;p&gt;Even if the Windows &lt;a href="https://en.wikipedia.org/wiki/Shortcut_(computing)" rel="noopener noreferrer"&gt;shortcut&lt;/a&gt; file is just a metafile used by the Windows File Explorer, it has been used by Cygwin to emulate symlinks. However, the Cygwin shortcut file cannot be read properly by the File Explorer since it lacks many of the expected header fields, whereas a shortcut file created in the File Explorer can be read by Cygwin.&lt;/p&gt;

&lt;p&gt;In addition to this, in Cygwin and MSYS2 there is a further complication to the creation of symlinks. In Cygwin, creation of symlinks depends on an environment variable called &lt;code&gt;CYGWIN&lt;/code&gt;. Depending of the content of this environment variable, the creation of broken symlinks may fail, or the creation of non-broken symlinks may fail if developer mode (see discussion above) is not activated. The environment variable also regulates whether the symlink will be created as a shortcut file or as a native symlink. &lt;/p&gt;

&lt;h4&gt;
  
  
  Behavior of &lt;code&gt;ln -s&lt;/code&gt; on Cygwin
&lt;/h4&gt;

&lt;p&gt;The behavior of the &lt;code&gt;ln --symbolic &amp;lt;target&amp;gt; &amp;lt;destination&amp;gt;&lt;/code&gt; command in Cygwin &lt;a href="https://cygwin.com/cygwin-ug-net/using-cygwinenv.html" rel="noopener noreferrer"&gt;depends on&lt;/a&gt; the environment variable &lt;code&gt;CYGWIN&lt;/code&gt; which is used to configure many global settings for the Cygwin runtime system. It contains options separated by blank characters. The option that is important for the &lt;code&gt;ln -s&lt;/code&gt; command is called &lt;code&gt;winsymlinks&lt;/code&gt;. According to the &lt;a href="https://cygwin.com/cygwin-ug-net/using-cygwinenv.html" rel="noopener noreferrer"&gt;Cygwin documentation&lt;/a&gt;, there are four cases for the &lt;code&gt;winsymlinks&lt;/code&gt; option to consider:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;winsymlinks&lt;/code&gt; is not defined. (Note: this behavior differs from that of MSYS2, see below). This is called the default behavior for Cygwin.&lt;br&gt;
a) If native symlinks are enabled (see discussion above), then this is equivalent to setting &lt;code&gt;winsymlinks&lt;/code&gt; to &lt;code&gt;native&lt;/code&gt; (e.g. &lt;code&gt;CYGWIN=winsymlinks:native&lt;/code&gt;), see 3) below.&lt;br&gt;
b) If native symlinks are not enabled, this is equivalent to setting &lt;code&gt;winsymlinks&lt;/code&gt; to &lt;code&gt;lnk&lt;/code&gt; see 2) below.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;winsymlinks&lt;/code&gt; is empty (&lt;code&gt;CYGWIN=winsymlinks&lt;/code&gt;) or &lt;code&gt;winsymlinks&lt;/code&gt; is set to &lt;code&gt;lnk&lt;/code&gt; (e.g. &lt;code&gt;CYGWIN=winsymlinks:lnk&lt;/code&gt;)&lt;br&gt;
Whether &amp;lt;target&amp;gt; exists or not, &lt;code&gt;ln -s&lt;/code&gt; creates  as a Windows shortcut file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;winsymlinks:native&lt;/code&gt;&lt;br&gt;
a) If native symlinks are enabled, and whether &amp;lt;target&amp;gt; exists or not, creates &amp;lt;destination&amp;gt; as a native Windows symlink. Note, this is most similar to the behavior of &lt;code&gt;ln -s&lt;/code&gt; on *nix.&lt;br&gt;
b) If native symlinks are not enabled, it is equivalent to setting &lt;code&gt;winsymlinks&lt;/code&gt; to &lt;code&gt;lnk&lt;/code&gt;, see 2) above.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;winsymlinks:nativestrict&lt;/code&gt;&lt;br&gt;
a) If native symlinks are enabled and &amp;lt;target&amp;gt; exists, creates &amp;lt;destination&amp;gt; as a native Windows symlink,&lt;br&gt;
b) else if native symlinks are not enabled or if &amp;lt;target&amp;gt; does not exist, &lt;code&gt;ln -s&lt;/code&gt; fails.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Behavior of &lt;code&gt;ln -s&lt;/code&gt; on MSYS2
&lt;/h4&gt;

&lt;p&gt;Similiarly to the &lt;code&gt;CYGWIN&lt;/code&gt; environment variable, the &lt;code&gt;MSYS&lt;/code&gt; environment variable is used to configure global settings for the MSYS2 runtime system (since MSYS2 is based on Cygwin). The four cases for the &lt;code&gt;winsymlinks&lt;/code&gt; option to consider is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;winsymlinks&lt;/code&gt; is not defined. The default behavior for MSYS2. (Note: this is not similar to Cygwin) &lt;br&gt;
a) If &amp;lt;target&amp;gt; exists, &amp;lt;target&amp;gt; is (surprise!!) &lt;a href="https://github.com/msys2/MSYS2-packages/issues/249" rel="noopener noreferrer"&gt;copied to&lt;/a&gt; &amp;lt;destination&amp;gt;, so &amp;lt;destination&amp;gt; does not become a symlink but simply a copy of &amp;lt;target&amp;gt;, this happens whether &amp;lt;target&amp;gt; is a file or a directory, or whether native symlinks are enabled or not. &lt;br&gt;
b) If &amp;lt;target&amp;gt; does not exist, &lt;code&gt;ln -s&lt;/code&gt; fails.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;winsymlinks&lt;/code&gt; or &lt;code&gt;winsymlinks:lnk&lt;/code&gt; : (Similar to Cygwin, see above)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;winsymlinks:native&lt;/code&gt; : (Similar to Cygwin, see above)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;winsymlinks:nativestrict&lt;/code&gt; : (Similar to Cygwin, see avove)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Behavior of symlinks in Perl on Windows (MSYS2 and Cygwin)
&lt;/h4&gt;

&lt;p&gt;In both MSYS2 and Cygwin,&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;     perl -MConfig -E'say $Config{d_symlink}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;prints &lt;code&gt;define&lt;/code&gt; (meaning the &lt;code&gt;symlink&lt;/code&gt; call is implemented), whereas in regular windows (CMD prompt and &lt;code&gt;$^O eq "MSWin32"&lt;/code&gt;) with e.g. strawberry perl, &lt;code&gt;$Config{d_symlink}&lt;/code&gt; is only defined for &lt;code&gt;perl&lt;/code&gt; versions &amp;gt;= 5.33.5, see &lt;a href="https://metacpan.org/release/CORION/perl-5.33.5/view/pod/perldelta.pod#Windows" rel="noopener noreferrer"&gt;perldelta&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So the perl &lt;code&gt;symlink&lt;/code&gt; function "works" on MSYS2 and Cygwin, and for newer versions of &lt;code&gt;MSWin32&lt;/code&gt;. However, since the newest &lt;a href="https://strawberryperl.com/releases.html" rel="noopener noreferrer"&gt;Strawberry perl release&lt;/a&gt; is currently at 5.32.1, I was not able to test how symlinks behave with &lt;code&gt;MSWin32&lt;/code&gt;. I will therefore in the following focus on Cygwin and MSYS2.&lt;/p&gt;

&lt;p&gt;It would be nice if one could easily check from within a Perl script if developer mode was on and thus native symlinks were enabled. However, the only way I found was to use XS (a C extension) to check the value of the registry key &lt;code&gt;SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the further discussion below, consider the Perl statement:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  symlink $target, $dest;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If &lt;code&gt;$dest&lt;/code&gt; exists, the &lt;a href="https://perldoc.perl.org/functions/symlink" rel="noopener noreferrer"&gt;&lt;code&gt;symlink&lt;/code&gt;&lt;/a&gt; command always fails (returning a value of 0 and setting &lt;code&gt;$!&lt;/code&gt;). So consider the case where &lt;code&gt;$dest&lt;/code&gt; does not exist: There are four cases for the &lt;code&gt;winsymlinks&lt;/code&gt; option contained in the &lt;code&gt;MSYS&lt;/code&gt; or &lt;code&gt;CYGWIN&lt;/code&gt; environment variable to consider, as was done above for the &lt;code&gt;ln -s&lt;/code&gt; command. It turns out that &lt;code&gt;symlink&lt;/code&gt; behaves identically to the &lt;code&gt;ln -s&lt;/code&gt; command, and when &lt;code&gt;ln -s&lt;/code&gt; fails, &lt;code&gt;symlink&lt;/code&gt; also fails and returns a value of 0 and sets &lt;code&gt;$!&lt;/code&gt; (&lt;code&gt;ERRNO&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Also note that the environment variables &lt;code&gt;MSYS&lt;/code&gt; or &lt;code&gt;CYGWIN&lt;/code&gt; cannot/should not be changed from within the Perl script itself. I am not sure why this does not work, but I tested it and it showed undefined behavior in my tests. So the variables should be set before &lt;code&gt;perl&lt;/code&gt; is run, e.g. on the command line:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  MSYS=winsymlinks:native perl p.pl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;What about the perl &lt;code&gt;-l&lt;/code&gt; operator ? Tests show that it does not differentiate between a Windows shortcut file and a native symlink file. So&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  say "symlink" if -l "foobar";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;prints "symlink" for both file types. Further, there seems to be no tool available to determine which of the two file types a given symlink file is. This means that when copying a symlink file, it is difficult to determine if the destination should be a native symlink or a windows shortcut. Hence, copying a symlink can silently convert a native symlink to a shortcut file depending on the setting of the &lt;code&gt;CYGWIN&lt;/code&gt; or &lt;code&gt;MSYS&lt;/code&gt; environment variable and this is also how the &lt;code&gt;cp&lt;/code&gt; command in MSYS2 (or Cygwin) &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; cp -a source destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;The behavior of symlinks is more complicated on Windows than on Linux. This is mainly a problem for programs that needs to copy directories. These programs may fail unexpectedly if they do not handle the different options in the &lt;code&gt;CYGWIN&lt;/code&gt; or &lt;code&gt;MSYS2&lt;/code&gt; environment variables. If the user does not set the environment variables, the default (meaning that the user did not set the &lt;code&gt;CYGWIN&lt;/code&gt; variable) behavior on Cygwin is such that it will never fail if the target of the symlink does not exist or if native symlinks are not enabled. However, the default behavior on MSYS2 is different. MSYS2 will by default &lt;em&gt;not&lt;/em&gt; create a symlink when a symlink is apparently created by calling &lt;code&gt;ln -s&lt;/code&gt;. Instead it creates a copy of the target file. If the target file does not exist, &lt;code&gt;ln -s&lt;/code&gt; fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Epilouge
&lt;/h3&gt;

&lt;p&gt;I was in the end able to install &lt;code&gt;Perl::LanguageServer&lt;/code&gt; on MSYS2, but when VS Code tried to use it, it crashed. I believe it was due to the module &lt;code&gt;IO::AIO&lt;/code&gt;, but I have not looked further into the issue. I also discovered a new Perl module called &lt;a href="https://metacpan.org/pod/PLS" rel="noopener noreferrer"&gt;PLS&lt;/a&gt;. This module is currently under &lt;a href="https://www.reddit.com/r/perl/comments/o9yfbp/pls_perl_language_server/" rel="noopener noreferrer"&gt;active development&lt;/a&gt; and implements &lt;a href="https://marketplace.visualstudio.com/items?itemName=FractalBoy.pls" rel="noopener noreferrer"&gt;features like&lt;/a&gt; auto-completion. However, it currently does not implement the &lt;a href="https://microsoft.github.io/debug-adapter-protocol/" rel="noopener noreferrer"&gt;Debug Adapter Protocol&lt;/a&gt; so debugging is not  available. This module also installed fine from my Windows CMD prompt (it uses &lt;a href="https://metacpan.org/pod/IO::Async" rel="noopener noreferrer"&gt;IO::Async&lt;/a&gt; instead of &lt;code&gt;IO::AIO&lt;/code&gt;), but it does not run from VS Code when I tested it. From the &lt;a href="https://www.reddit.com/r/perl/comments/o9yfbp/pls_perl_language_server/" rel="noopener noreferrer"&gt;discussion&lt;/a&gt; on reddit I guess this problem will hopefully be fixed soon.&lt;/p&gt;

</description>
      <category>perl</category>
      <category>windows</category>
      <category>msys2</category>
      <category>cygwin</category>
    </item>
  </channel>
</rss>
